diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..a7f4291257 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,73 @@ +module.exports = { + "root": true, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 5 + }, + "globals": { + "Account": false, + "AnimationCache": false, + "Assets": false, + "Audio": false, + "AudioDevice": false, + "AudioEffectOptions": false, + "AvatarList": false, + "AvatarManager": false, + "Camera": false, + "Clipboard": false, + "Controller": false, + "DialogsManager": false, + "Entities": false, + "FaceTracker": false, + "GlobalServices": false, + "HMD": false, + "LODManager": false, + "Mat4": false, + "Menu": false, + "Messages": false, + "ModelCache": false, + "MyAvatar": false, + "Overlays": false, + "Paths": false, + "Quat": false, + "Rates": false, + "Recording": false, + "Reticle": false, + "Scene": false, + "Script": false, + "ScriptDiscoveryService": false, + "Settings": false, + "SoundCache": false, + "Stats": false, + "TextureCache": false, + "Uuid": false, + "UndoStack": false, + "Vec3": false, + "WebSocket": false, + "WebWindow": false, + "Window": false, + "XMLHttpRequest": false, + "location": false, + "print": false + }, + "rules": { + "brace-style": ["error", "1tbs", { "allowSingleLine": false }], + "comma-dangle": ["error", "never"], + "camelcase": ["error"], + "curly": ["error", "all"], + "indent": ["error", 4, { "SwitchCase": 1 }], + "keyword-spacing": ["error", { "before": true, "after": true }], + "max-len": ["error", 128, 4], + "new-cap": ["error"], + "no-floating-decimal": ["error"], + //"no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreArrayIndexes": true }], + "no-multiple-empty-lines": ["error"], + "no-multi-spaces": ["error"], + "no-unused-vars": ["error", { "args": "none", "vars": "local" }], + "semi": ["error", "always"], + "spaced-comment": ["error", "always", { + "line": { "markers": ["/"] } + }], + "space-before-function-paren": ["error", {"anonymous": "always", "named": "never"}] + } +}; diff --git a/BUILD_OSX.md b/BUILD_OSX.md index c8f19710ca..1c9c5a9796 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -3,22 +3,23 @@ Please read the [general build guide](BUILD.md) for information on dependencies ###Homebrew [Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all High Fidelity dependencies very simple. - brew install cmake openssl qt5 + brew tap homebrew/versions + brew install cmake openssl qt55 -We no longer require install of qt5 via our [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas). Versions of Qt that are 5.5.x and above provide a mechanism to disable the wireless scanning we previously had a custom patch for. +We no longer require install of qt5 via our [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas). Versions of Qt that are 5.5.x provide a mechanism to disable the wireless scanning we previously had a custom patch for. ###OpenSSL and Qt Assuming you've installed OpenSSL or Qt 5 using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR and QT_CMAKE_PREFIX_PATH so CMake can find your installations. For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: - export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2d_1 + export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2h_1/ For Qt 5.5.1 installed via homebrew, set QT_CMAKE_PREFIX_PATH as follows. - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.5.1_2/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt55/5.5.1/lib/cmake -Not that these use the versions from homebrew formulae at the time of this writing, and the version in the path will likely change. +Note that these use the versions from homebrew formulae at the time of this writing, and the version in the path will likely change. ###Xcode If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. diff --git a/CMakeLists.txt b/CMakeLists.txt index 0922779bc6..0d42be3d95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,8 +226,8 @@ if (NOT ANDROID) add_subdirectory(interface) set_target_properties(interface PROPERTIES FOLDER "Apps") add_subdirectory(tests) - add_subdirectory(plugins) endif() + add_subdirectory(plugins) add_subdirectory(tools) endif() diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 1b5840c3c8..d0fd2c1176 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -6,7 +6,7 @@ setup_hifi_project(Core Gui Network Script Quick Widgets WebSockets) link_hifi_libraries( audio avatars octree gpu model fbx entities networking animation recording shared script-engine embedded-webserver - controllers physics + controllers physics plugins ) if (WIN32) diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 0455377d89..4fc8975262 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -51,6 +51,8 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri LogUtils::init(); QSettings::setDefaultFormat(QSettings::IniFormat); + + DependencyManager::set(); auto scriptableAvatar = DependencyManager::set(); auto addressManager = DependencyManager::set(); @@ -116,7 +118,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri _requestTimer.start(ASSIGNMENT_REQUEST_INTERVAL_MSECS); // connections to AccountManager for authentication - connect(&AccountManager::getInstance(), &AccountManager::authRequired, + connect(DependencyManager::get().data(), &AccountManager::authRequired, this, &AssignmentClient::handleAuthenticationRequest); // Create Singleton objects on main thread @@ -309,13 +311,13 @@ void AssignmentClient::handleAuthenticationRequest() { QString username = sysEnvironment.value(DATA_SERVER_USERNAME_ENV); QString password = sysEnvironment.value(DATA_SERVER_PASSWORD_ENV); - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); if (!username.isEmpty() && !password.isEmpty()) { // ask the account manager to log us in from the env variables - accountManager.requestAccessToken(username, password); + accountManager->requestAccessToken(username, password); } else { - qCWarning(assigmnentclient) << "Authentication was requested against" << qPrintable(accountManager.getAuthURL().toString()) + qCWarning(assigmnentclient) << "Authentication was requested against" << qPrintable(accountManager->getAuthURL().toString()) << "but both or one of" << qPrintable(DATA_SERVER_USERNAME_ENV) << "/" << qPrintable(DATA_SERVER_PASSWORD_ENV) << "are not set. Unable to authenticate."; diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 322fe6e57e..8ba253d549 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -286,8 +286,8 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer()->addOrUpdateNode - (senderID, NodeType::Unassigned, senderSockAddr, senderSockAddr, false, false); + matchingNode = DependencyManager::get()->addOrUpdateNode(senderID, NodeType::Unassigned, + senderSockAddr, senderSockAddr); auto childData = std::unique_ptr { new AssignmentClientChildData(Assignment::Type::AllTypes) }; diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 1fb0674e7d..905cc6fd30 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -235,7 +235,7 @@ void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedN } void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { - if (senderNode->getCanRez()) { + if (senderNode->getCanWriteToAssetServer()) { QString assetPath = message.readString(); auto assetHash = message.read(SHA256_HASH_LENGTH).toHex(); @@ -251,7 +251,7 @@ void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNode } void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { - if (senderNode->getCanRez()) { + if (senderNode->getCanWriteToAssetServer()) { int numberOfDeletedMappings { 0 }; message.readPrimitive(&numberOfDeletedMappings); @@ -272,7 +272,7 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared } void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { - if (senderNode->getCanRez()) { + if (senderNode->getCanWriteToAssetServer()) { QString oldPath = message.readString(); QString newPath = message.readString(); @@ -298,7 +298,8 @@ void AssetServer::handleAssetGetInfo(QSharedPointer message, Sh message->readPrimitive(&messageID); assetHash = message->readWithoutCopy(SHA256_HASH_LENGTH); - auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply); + auto size = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(AssetServerError) + sizeof(qint64)); + auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply, size, true); QByteArray hexHash = assetHash.toHex(); @@ -337,7 +338,7 @@ void AssetServer::handleAssetGet(QSharedPointer message, Shared void AssetServer::handleAssetUpload(QSharedPointer message, SharedNodePointer senderNode) { - if (senderNode->getCanRez()) { + if (senderNode->getCanWriteToAssetServer()) { qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID()); auto task = new UploadAssetTask(message, senderNode, _filesDirectory); @@ -347,7 +348,7 @@ void AssetServer::handleAssetUpload(QSharedPointer message, Sha // for now this also means it isn't allowed to add assets // so return a packet with error that indicates that - auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError)); + auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError), true); MessageID messageID; message->readPrimitive(&messageID); diff --git a/assignment-client/src/assets/UploadAssetTask.cpp b/assignment-client/src/assets/UploadAssetTask.cpp index 5ca9b5bbf1..e09619a3cc 100644 --- a/assignment-client/src/assets/UploadAssetTask.cpp +++ b/assignment-client/src/assets/UploadAssetTask.cpp @@ -43,7 +43,7 @@ void UploadAssetTask::run() { qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID()); - auto replyPacket = NLPacket::create(PacketType::AssetUploadReply); + auto replyPacket = NLPacket::create(PacketType::AssetUploadReply, -1, true); replyPacket->writePrimitive(messageID); if (fileSize > MAX_UPLOAD_SIZE) { diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 0f51bd00b1..8f752e70d0 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -47,6 +47,8 @@ #include #include #include +#include +#include #include #include #include @@ -60,7 +62,7 @@ #include "AudioMixer.h" const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f; -const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.18f; +const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance) const float DEFAULT_NOISE_MUTING_THRESHOLD = 0.003f; const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer"; const QString AUDIO_ENV_GROUP_KEY = "audio_env"; @@ -90,6 +92,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : PacketType::AudioStreamStats }, this, "handleNodeAudioPacket"); packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); + packetReceiver.registerListener(PacketType::NegotiateAudioFormat, this, "handleNegotiateAudioFormat"); + packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled); } @@ -137,13 +141,14 @@ float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd, } if (distanceBetween >= ATTENUATION_BEGINS_AT_DISTANCE) { - // calculate the distance coefficient using the distance to this node - float distanceCoefficient = 1.0f - (logf(distanceBetween / ATTENUATION_BEGINS_AT_DISTANCE) / logf(2.0f) - * attenuationPerDoublingInDistance); - if (distanceCoefficient < 0) { - distanceCoefficient = 0; - } + // translate the zone setting to gain per log2(distance) + float g = 1.0f - attenuationPerDoublingInDistance; + g = (g < EPSILON) ? EPSILON : g; + g = (g > 1.0f) ? 1.0f : g; + + // calculate the distance coefficient using the distance to this node + float distanceCoefficient = exp2f(log2f(g) * log2f(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE)); // multiply the current attenuation coefficient by the distance coefficient gain *= distanceCoefficient; @@ -189,8 +194,12 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData& // check if this is a server echo of a source back to itself bool isEcho = (&streamToAdd == &listeningNodeStream); - // figure out the gain for this source at the listener glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition(); + + // figure out the distance between source and listener + float distance = glm::max(glm::length(relativePosition), EPSILON); + + // figure out the gain for this source at the listener float gain = gainForSource(streamToAdd, listeningNodeStream, relativePosition, isEcho); // figure out the azimuth to this source at the listener @@ -236,7 +245,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData& // this is not done for stereo streams since they do not go through the HRTF static int16_t silentMonoBlock[AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL] = {}; - hrtf.renderSilent(silentMonoBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, gain, + hrtf.renderSilent(silentMonoBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); ++_hrtfSilentRenders;; @@ -283,7 +292,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData& // silent frame from source // we still need to call renderSilent via the HRTF for mono source - hrtf.renderSilent(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, gain, + hrtf.renderSilent(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); ++_hrtfSilentRenders; @@ -296,7 +305,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData& // the mixer is struggling so we're going to drop off some streams // we call renderSilent via the HRTF with the actual frame data and a gain of 0.0 - hrtf.renderSilent(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, 0.0f, + hrtf.renderSilent(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, distance, 0.0f, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); ++_hrtfStruggleRenders; @@ -307,7 +316,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData& ++_hrtfRenders; // mono stream, call the HRTF with our block and calculated azimuth and gain - hrtf.render(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, gain, + hrtf.render(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); } @@ -321,7 +330,8 @@ bool AudioMixer::prepareMixForListeningNode(Node* node) { // loop through all other nodes that have sufficient audio to mix DependencyManager::get()->eachNode([&](const SharedNodePointer& otherNode){ - if (otherNode->getLinkedData()) { + // make sure that we have audio data for this other node and that it isn't being ignored by our listening node + if (otherNode->getLinkedData() && !node->isIgnoringNodeWithID(otherNode->getUUID())) { AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData(); // enumerate the ARBs attached to the otherNode and add all that should be added to mix @@ -339,21 +349,18 @@ bool AudioMixer::prepareMixForListeningNode(Node* node) { } }); - int nonZeroSamples = 0; + // use the per listner AudioLimiter to render the mixed data... + listenerNodeData->audioLimiter.render(_mixedSamples, _clampedSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); - // enumerate the mixed samples and clamp any samples outside the min/max - // also check if we ended up with a silent frame + // check for silent audio after the peak limitor has converted the samples + bool hasAudio = false; for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; ++i) { - - _clampedSamples[i] = int16_t(glm::clamp(int(_mixedSamples[i] * AudioConstants::MAX_SAMPLE_VALUE), - AudioConstants::MIN_SAMPLE_VALUE, - AudioConstants::MAX_SAMPLE_VALUE)); - if (_clampedSamples[i] != 0.0f) { - ++nonZeroSamples; + if (_clampedSamples[i] != 0) { + hasAudio = true; + break; } } - - return (nonZeroSamples > 0); + return hasAudio; } void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) { @@ -449,6 +456,91 @@ void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer mes } } +DisplayPluginList getDisplayPlugins() { + DisplayPluginList result; + return result; +} + +InputPluginList getInputPlugins() { + InputPluginList result; + return result; +} + +void saveInputPluginSettings(const InputPluginList& plugins) { +} + + +void AudioMixer::handleNegotiateAudioFormat(QSharedPointer message, SharedNodePointer sendingNode) { + QStringList availableCodecs; + auto codecPlugins = PluginManager::getInstance()->getCodecPlugins(); + if (codecPlugins.size() > 0) { + for (auto& plugin : codecPlugins) { + auto codecName = plugin->getName(); + qDebug() << "Codec available:" << codecName; + availableCodecs.append(codecName); + } + } else { + qDebug() << "No Codecs available..."; + } + + CodecPluginPointer selectedCodec; + QString selectedCodecName; + + QStringList codecPreferenceList = _codecPreferenceOrder.split(","); + + // read the codecs requested by the client + const int MAX_PREFERENCE = 99999; + int preferredCodecIndex = MAX_PREFERENCE; + QString preferredCodec; + quint8 numberOfCodecs = 0; + message->readPrimitive(&numberOfCodecs); + qDebug() << "numberOfCodecs:" << numberOfCodecs; + QStringList codecList; + for (quint16 i = 0; i < numberOfCodecs; i++) { + QString requestedCodec = message->readString(); + int preferenceOfThisCodec = codecPreferenceList.indexOf(requestedCodec); + bool codecAvailable = availableCodecs.contains(requestedCodec); + qDebug() << "requestedCodec:" << requestedCodec << "preference:" << preferenceOfThisCodec << "available:" << codecAvailable; + if (codecAvailable) { + codecList.append(requestedCodec); + if (preferenceOfThisCodec >= 0 && preferenceOfThisCodec < preferredCodecIndex) { + qDebug() << "This codec is preferred..."; + selectedCodecName = requestedCodec; + preferredCodecIndex = preferenceOfThisCodec; + } + } + } + qDebug() << "all requested and available codecs:" << codecList; + + // choose first codec + if (!selectedCodecName.isEmpty()) { + if (codecPlugins.size() > 0) { + for (auto& plugin : codecPlugins) { + if (selectedCodecName == plugin->getName()) { + qDebug() << "Selecting codec:" << selectedCodecName; + selectedCodec = plugin; + break; + } + } + } + } + + auto clientData = dynamic_cast(sendingNode->getLinkedData()); + + // FIXME - why would we not have client data at this point?? + if (!clientData) { + qDebug() << "UNEXPECTED -- didn't have node linked data in " << __FUNCTION__; + sendingNode->setLinkedData(std::unique_ptr { new AudioMixerClientData(sendingNode->getUUID()) }); + clientData = dynamic_cast(sendingNode->getLinkedData()); + connect(clientData, &AudioMixerClientData::injectorStreamFinished, this, &AudioMixer::removeHRTFsForFinishedInjector); + } + + clientData->setupCodec(selectedCodec, selectedCodecName); + + qDebug() << "selectedCodecName:" << selectedCodecName; + clientData->sendSelectAudioFormat(sendingNode, selectedCodecName); +} + void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) { // enumerate the connected listeners to remove HRTF objects for the disconnected node auto nodeList = DependencyManager::get(); @@ -461,6 +553,10 @@ void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) { }); } +void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + sendingNode->parseIgnoreRequestMessage(packet); +} + void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) { auto injectorClientData = qobject_cast(sender()); if (injectorClientData) { @@ -665,24 +761,36 @@ void AudioMixer::broadcastMixes() { std::unique_ptr mixPacket; if (mixHasAudio) { - int mixPacketBytes = sizeof(quint16) + AudioConstants::NETWORK_FRAME_BYTES_STEREO; + int mixPacketBytes = sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE + + AudioConstants::NETWORK_FRAME_BYTES_STEREO; mixPacket = NLPacket::create(PacketType::MixedAudio, mixPacketBytes); // pack sequence number quint16 sequence = nodeData->getOutgoingSequenceNumber(); mixPacket->writePrimitive(sequence); + // write the codec + QString codecInPacket = nodeData->getCodecName(); + mixPacket->writeString(codecInPacket); + + QByteArray decodedBuffer(reinterpret_cast(_clampedSamples), AudioConstants::NETWORK_FRAME_BYTES_STEREO); + QByteArray encodedBuffer; + nodeData->encode(decodedBuffer, encodedBuffer); + // pack mixed audio samples - mixPacket->write(reinterpret_cast(_clampedSamples), - AudioConstants::NETWORK_FRAME_BYTES_STEREO); + mixPacket->write(encodedBuffer.constData(), encodedBuffer.size()); } else { - int silentPacketBytes = sizeof(quint16) + sizeof(quint16); + int silentPacketBytes = sizeof(quint16) + sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE; mixPacket = NLPacket::create(PacketType::SilentAudioFrame, silentPacketBytes); // pack sequence number quint16 sequence = nodeData->getOutgoingSequenceNumber(); mixPacket->writePrimitive(sequence); + // write the codec + QString codecInPacket = nodeData->getCodecName(); + mixPacket->writeString(codecInPacket); + // pack number of silent audio samples quint16 numSilentSamples = AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; mixPacket->writePrimitive(numSilentSamples); @@ -800,6 +908,12 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) { if (settingsObject.contains(AUDIO_ENV_GROUP_KEY)) { QJsonObject audioEnvGroupObject = settingsObject[AUDIO_ENV_GROUP_KEY].toObject(); + const QString CODEC_PREFERENCE_ORDER = "codec_preference_order"; + if (audioEnvGroupObject[CODEC_PREFERENCE_ORDER].isString()) { + _codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString(); + qDebug() << "Codec preference order changed to" << _codecPreferenceOrder; + } + const QString ATTENATION_PER_DOULING_IN_DISTANCE = "attenuation_per_doubling_in_distance"; if (audioEnvGroupObject[ATTENATION_PER_DOULING_IN_DISTANCE].isString()) { bool ok = false; diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 24b4b39704..9b26989847 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -45,7 +45,9 @@ private slots: void broadcastMixes(); void handleNodeAudioPacket(QSharedPointer packet, SharedNodePointer sendingNode); void handleMuteEnvironmentPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void handleNegotiateAudioFormat(QSharedPointer message, SharedNodePointer sendingNode); void handleNodeKilled(SharedNodePointer killedNode); + void handleNodeIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void removeHRTFsForFinishedInjector(const QUuid& streamID); @@ -91,6 +93,8 @@ private: int _manualEchoMixes { 0 }; int _totalMixes { 0 }; + QString _codecPreferenceOrder; + float _mixedSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO]; int16_t _clampedSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO]; diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 93a51b1df2..85491537a2 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -25,6 +25,7 @@ AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) : NodeData(nodeID), + audioLimiter(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO), _outgoingMixedAudioSequenceNumber(0), _downstreamAudioStreamStats() { @@ -38,6 +39,14 @@ AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) : _frameToSendStats = distribution(numberGenerator); } +AudioMixerClientData::~AudioMixerClientData() { + if (_codec) { + _codec->releaseDecoder(_decoder); + _codec->releaseEncoder(_encoder); + } +} + + AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() { QReadLocker readLocker { &_streamsLock }; @@ -100,9 +109,15 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) { bool isStereo = channelFlag == 1; + auto avatarAudioStream = new AvatarAudioStream(isStereo, AudioMixer::getStreamSettings()); + avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO); + qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName; + + connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioMixerClientData::sendSelectAudioFormat); + auto emplaced = _audioStreams.emplace( QUuid(), - std::unique_ptr { new AvatarAudioStream(isStereo, AudioMixer::getStreamSettings()) } + std::unique_ptr { avatarAudioStream } ); micStreamIt = emplaced.first; @@ -115,7 +130,6 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) { isMicStream = true; } else if (packetType == PacketType::InjectAudio) { // this is injected audio - // grab the stream identifier for this injected audio message.seek(sizeof(quint16)); QUuid streamIdentifier = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); @@ -129,9 +143,16 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) { if (streamIt == _audioStreams.end()) { // we don't have this injected stream yet, so add it + auto injectorStream = new InjectedAudioStream(streamIdentifier, isStereo, AudioMixer::getStreamSettings()); + +#if INJECTORS_SUPPORT_CODECS + injectorStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO); + qDebug() << "creating new injectorStream... codec:" << _selectedCodecName; +#endif + auto emplaced = _audioStreams.emplace( streamIdentifier, - std::unique_ptr { new InjectedAudioStream(streamIdentifier, isStereo, AudioMixer::getStreamSettings()) } + std::unique_ptr { injectorStream } ); streamIt = emplaced.first; @@ -323,3 +344,52 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() { return result; } + +void AudioMixerClientData::sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName) { + auto replyPacket = NLPacket::create(PacketType::SelectedAudioFormat); + replyPacket->writeString(selectedCodecName); + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(replyPacket), *node); +} + + +void AudioMixerClientData::setupCodec(CodecPluginPointer codec, const QString& codecName) { + cleanupCodec(); // cleanup any previously allocated coders first + _codec = codec; + _selectedCodecName = codecName; + if (codec) { + _encoder = codec->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO); + _decoder = codec->createDecoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO); + } + + auto avatarAudioStream = getAvatarAudioStream(); + if (avatarAudioStream) { + avatarAudioStream->setupCodec(codec, codecName, AudioConstants::MONO); + } + +#if INJECTORS_SUPPORT_CODECS + // fixup codecs for any active injectors... + auto it = _audioStreams.begin(); + while (it != _audioStreams.end()) { + SharedStreamPointer stream = it->second; + if (stream->getType() == PositionalAudioStream::Injector) { + stream->setupCodec(codec, codecName, stream->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO); + } + ++it; + } +#endif +} + +void AudioMixerClientData::cleanupCodec() { + // release any old codec encoder/decoder first... + if (_codec) { + if (_decoder) { + _codec->releaseDecoder(_decoder); + _decoder = nullptr; + } + if (_encoder) { + _codec->releaseEncoder(_encoder); + _encoder = nullptr; + } + } +} diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index ff4143cf08..babfae3539 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -16,15 +16,20 @@ #include #include +#include #include +#include + #include "PositionalAudioStream.h" #include "AvatarAudioStream.h" + class AudioMixerClientData : public NodeData { Q_OBJECT public: AudioMixerClientData(const QUuid& nodeID); + ~AudioMixerClientData(); using SharedStreamPointer = std::shared_ptr; using AudioStreamMap = std::unordered_map; @@ -61,9 +66,26 @@ public: // uses randomization to have the AudioMixer send a stats packet to this node around every second bool shouldSendStats(int frameNumber); + AudioLimiter audioLimiter; + + void setupCodec(CodecPluginPointer codec, const QString& codecName); + void cleanupCodec(); + void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) { + if (_encoder) { + _encoder->encode(decodedBuffer, encodedBuffer); + } else { + encodedBuffer = decodedBuffer; + } + } + + QString getCodecName() { return _selectedCodecName; } + signals: void injectorStreamFinished(const QUuid& streamIdentifier); +public slots: + void sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName); + private: QReadWriteLock _streamsLock; AudioStreamMap _audioStreams; // microphone stream from avatar is stored under key of null UUID @@ -77,6 +99,11 @@ private: AudioStreamStats _downstreamAudioStreamStats; int _frameToSendStats { 0 }; + + CodecPluginPointer _codec; + QString _selectedCodecName; + Encoder* _encoder{ nullptr }; // for outbound mixed stream + Decoder* _decoder{ nullptr }; // for mic stream }; #endif // hifi_AudioMixerClientData_h diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index a109934d10..65989b389e 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -45,6 +45,10 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::AvatarData, this, "handleAvatarDataPacket"); packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); + packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); + + auto nodeList = DependencyManager::get(); + connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch); } AvatarMixer::~AvatarMixer() { @@ -224,14 +228,15 @@ void AvatarMixer::broadcastAvatarData() { // send back a packet with other active node data to this node nodeList->eachMatchingNode( [&](const SharedNodePointer& otherNode)->bool { - if (!otherNode->getLinkedData()) { + // make sure we have data for this avatar, that it isn't the same node, + // and isn't an avatar that the viewing node has ignored + if (!otherNode->getLinkedData() + || otherNode->getUUID() == node->getUUID() + || node->isIgnoringNodeWithID(otherNode->getUUID())) { return false; + } else { + return true; } - if (otherNode->getUUID() == node->getUUID()) { - return false; - } - - return true; }, [&](const SharedNodePointer& otherNode) { ++numOtherAvatars; @@ -414,7 +419,9 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes AvatarData& avatar = nodeData->getAvatar(); // parse the identity packet and update the change timestamp if appropriate - if (avatar.hasIdentityChangedAfterParsing(message->getMessage())) { + AvatarData::Identity identity; + AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity); + if (avatar.processAvatarIdentity(identity)) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); nodeData->flagIdentityChange(); } @@ -426,6 +433,10 @@ void AvatarMixer::handleKillAvatarPacket(QSharedPointer message DependencyManager::get()->processKillNode(*message); } +void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer message, SharedNodePointer senderNode) { + senderNode->parseIgnoreRequestMessage(message); +} + void AvatarMixer::sendStatsPacket() { QJsonObject statsObject; statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; @@ -509,6 +520,19 @@ void AvatarMixer::domainSettingsRequestComplete() { _broadcastThread.start(); } +void AvatarMixer::handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID) { + // if this client is using packet versions we don't expect. + if ((type == PacketTypeEnum::Value::AvatarIdentity || type == PacketTypeEnum::Value::AvatarData) && !senderUUID.isNull()) { + // Echo an empty AvatarData packet back to that client. + // This should trigger a version mismatch dialog on their side. + auto nodeList = DependencyManager::get(); + auto node = nodeList->nodeWithUUID(senderUUID); + if (node) { + auto emptyPacket = NLPacket::create(PacketType::AvatarData, 0); + nodeList->sendPacket(std::move(emptyPacket), *node); + } + } +} void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer"; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index c7761a2cba..00cf457d40 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -37,8 +37,11 @@ private slots: void handleAvatarDataPacket(QSharedPointer message, SharedNodePointer senderNode); void handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode); void handleKillAvatarPacket(QSharedPointer message); + void handleNodeIgnoreRequestPacket(QSharedPointer message, SharedNodePointer senderNode); void domainSettingsRequestComplete(); - + void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); + + private: void broadcastAvatarData(); void parseDomainServerSettings(const QJsonObject& domainSettings); diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 0555f95c65..7594d5dd2c 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -268,6 +268,14 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio qDebug("wantTerseEditLogging=%s", debug::valueOf(wantTerseEditLogging)); EntityTreePointer tree = std::static_pointer_cast(_tree); + + int maxTmpEntityLifetime; + if (readOptionInt("maxTmpLifetime", settingsSectionObject, maxTmpEntityLifetime)) { + tree->setEntityMaxTmpLifetime(maxTmpEntityLifetime); + } else { + tree->setEntityMaxTmpLifetime(EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME); + } + tree->setWantEditLogging(wantEditLogging); tree->setWantTerseEditLogging(wantTerseEditLogging); } diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index 1685f08e01..0486a97ede 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -60,8 +60,8 @@ public: virtual void trackViewerGone(const QUuid& sessionID) override; public slots: - virtual void nodeAdded(SharedNodePointer node); - virtual void nodeKilled(SharedNodePointer node); + virtual void nodeAdded(SharedNodePointer node) override; + virtual void nodeKilled(SharedNodePointer node) override; void pruneDeletedEntities(); protected: diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index c5d010871c..d2fef4dfbd 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -153,7 +153,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer qDebug() << " maxSize=" << maxSize; qDebug("OctreeInboundPacketProcessor::processPacket() %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld maxSize=%d", - packetType, message->getRawMessage(), message->getSize(), editData, + (unsigned char)packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition(), maxSize); } @@ -191,7 +191,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld", - packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition()); + (unsigned char)packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition()); } // Make sure our Node and NodeList knows we've heard from this node. @@ -208,7 +208,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer } trackInboundPacket(nodeUUID, sequence, transitTime, editsInPacket, processTime, lockWaitTime); } else { - qDebug("unknown packet ignored... packetType=%hhu", packetType); + qDebug("unknown packet ignored... packetType=%hhu", (unsigned char)packetType); } } diff --git a/cmake/externals/hifiAudioCodec/CMakeLists.txt b/cmake/externals/hifiAudioCodec/CMakeLists.txt new file mode 100644 index 0000000000..87b5044b42 --- /dev/null +++ b/cmake/externals/hifiAudioCodec/CMakeLists.txt @@ -0,0 +1,43 @@ +include(ExternalProject) +include(SelectLibraryConfigurations) + +set(EXTERNAL_NAME hifiAudioCodec) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +if (WIN32 OR APPLE) + ExternalProject_Add( + ${EXTERNAL_NAME} + URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-1.zip + URL_MD5 23ec3fe51eaa155ea159a4971856fc13 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 + ) +elseif(NOT ANDROID) + ExternalProject_Add( + ${EXTERNAL_NAME} + URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux.zip + URL_MD5 7d37914a18aa4de971d2f45dd3043bde + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 + ) +endif() + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) + +if (WIN32) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL) +elseif(APPLE) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) +elseif(NOT ANDROID) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) +endif() diff --git a/cmake/externals/neuron/CMakeLists.txt b/cmake/externals/neuron/CMakeLists.txt index 6936725571..76dda8f8c5 100644 --- a/cmake/externals/neuron/CMakeLists.txt +++ b/cmake/externals/neuron/CMakeLists.txt @@ -4,8 +4,8 @@ set(EXTERNAL_NAME neuron) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -set(NEURON_URL "https://s3.amazonaws.com/hifi-public/dependencies/neuron_datareader_b.12.zip") -set(NEURON_URL_MD5 "0ab54ca04c9cc8094e0fa046c226e574") +set(NEURON_URL "https://s3.amazonaws.com/hifi-public/dependencies/neuron_datareader_b.12.2.zip") +set(NEURON_URL_MD5 "84273ad2200bf86a9279d1f412a822ca") ExternalProject_Add(${EXTERNAL_NAME} URL ${NEURON_URL} diff --git a/cmake/externals/sixense/CMakeLists.txt b/cmake/externals/sixense/CMakeLists.txt index 16f2850449..bd0d042c0b 100644 --- a/cmake/externals/sixense/CMakeLists.txt +++ b/cmake/externals/sixense/CMakeLists.txt @@ -57,30 +57,7 @@ if (WIN32) elseif(APPLE) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/osx_x64/release_dll/libsixense_x64.dylib CACHE TYPE INTERNAL) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${SOURCE_DIR}/lib/osx_x64/debug_dll/libsixensed_x64.dylib CACHE TYPE INTERNAL) - - set(_SIXENSE_LIB_DIR "${SOURCE_DIR}/lib/osx_x64") - ExternalProject_Add_Step( - ${EXTERNAL_NAME} - change-install-name-release - COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" - COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_SIXENSE_LIB_DIR}/release_dll -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake - DEPENDEES install - WORKING_DIRECTORY - LOG 1 - ) - - set(_SIXENSE_LIB_DIR "${SOURCE_DIR}/lib/osx_x64") - ExternalProject_Add_Step( - ${EXTERNAL_NAME} - change-install-name-debug - COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" - COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_SIXENSE_LIB_DIR}/debug_dll -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake - DEPENDEES install - WORKING_DIRECTORY - LOG 1 - ) + # We no longer support Sixense on Macs due to bugs in the Sixense DLL elseif(NOT ANDROID) diff --git a/cmake/externals/steamworks/CMakeLists.txt b/cmake/externals/steamworks/CMakeLists.txt new file mode 100644 index 0000000000..152e95cdcf --- /dev/null +++ b/cmake/externals/steamworks/CMakeLists.txt @@ -0,0 +1,62 @@ +include(ExternalProject) + +set(EXTERNAL_NAME steamworks) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +set(STEAMWORKS_URL "https://s3.amazonaws.com/hifi-public/dependencies/steamworks_sdk_137.zip") +set(STEAMWORKS_URL_MD5 "95ba9d0e3ddc04f8a8be17d2da806cbb") + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL ${STEAMWORKS_URL} + URL_MD5 ${STEAMWORKS_URL_MD5} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/public CACHE TYPE INTERNAL) + +if (WIN32) + + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(ARCH_DIR ${SOURCE_DIR}/redistributable_bin/win64) + set(ARCH_SUFFIX "64") + else() + set(ARCH_DIR ${SOURCE_DIR}/redistributable_bin) + set(ARCH_SUFFIX "") + endif() + + set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${ARCH_DIR}) + set(${EXTERNAL_NAME_UPPER}_LIB_PATH ${ARCH_DIR}) + + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/steam_api${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL) + add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_DLL_PATH}") + +elseif(APPLE) + + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/osx32/libsteam_api.dylib CACHE TYPE INTERNAL) + + set(_STEAMWORKS_LIB_DIR "${SOURCE_DIR}/redistributable_bin/osx32") + ExternalProject_Add_Step( + ${EXTERNAL_NAME} + change-install-name + COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" + COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_STEAMWORKS_LIB_DIR} -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake + DEPENDEES install + WORKING_DIRECTORY + LOG 1 + ) + +elseif(NOT ANDROID) + + # FIXME need to account for different architectures + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/linux64/libsteam_api.so CACHE TYPE INTERNAL) + +endif() diff --git a/cmake/macros/AddDependencyExternalProjects.cmake b/cmake/macros/AddDependencyExternalProjects.cmake index e35ca98959..99b8317fd7 100644 --- a/cmake/macros/AddDependencyExternalProjects.cmake +++ b/cmake/macros/AddDependencyExternalProjects.cmake @@ -43,4 +43,4 @@ macro(ADD_DEPENDENCY_EXTERNAL_PROJECTS) endforeach() -endmacro() \ No newline at end of file +endmacro() diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index f51455e9d4..dfa59943d6 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -44,6 +44,8 @@ function(AUTOSCRIBE_SHADER SHADER_FILE) set(SHADER_TARGET ${SHADER_TARGET}_vert.h) elseif(${SHADER_EXT} STREQUAL .slf) set(SHADER_TARGET ${SHADER_TARGET}_frag.h) + elseif(${SHADER_EXT} STREQUAL .slg) + set(SHADER_TARGET ${SHADER_TARGET}_geom.h) endif() set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_TARGET}") @@ -87,7 +89,7 @@ macro(AUTOSCRIBE_SHADER_LIB) #message(${HIFI_LIBRARIES_SHADER_INCLUDE_FILES}) file(GLOB_RECURSE SHADER_INCLUDE_FILES src/*.slh) - file(GLOB_RECURSE SHADER_SOURCE_FILES src/*.slv src/*.slf) + file(GLOB_RECURSE SHADER_SOURCE_FILES src/*.slv src/*.slf src/*.slg) #make the shader folder set(SHADERS_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders/${TARGET_NAME}") diff --git a/cmake/macros/InstallBesideConsole.cmake b/cmake/macros/InstallBesideConsole.cmake index 318a7a3ffe..0eb6025f38 100644 --- a/cmake/macros/InstallBesideConsole.cmake +++ b/cmake/macros/InstallBesideConsole.cmake @@ -16,6 +16,7 @@ macro(install_beside_console) install( TARGETS ${TARGET_NAME} RUNTIME DESTINATION ${COMPONENT_INSTALL_DIR} + LIBRARY DESTINATION ${CONSOLE_PLUGIN_INSTALL_DIR} COMPONENT ${SERVER_COMPONENT} ) else () diff --git a/cmake/macros/OptionalWinExecutableSigning.cmake b/cmake/macros/OptionalWinExecutableSigning.cmake index 784aae716f..41ca5762dc 100644 --- a/cmake/macros/OptionalWinExecutableSigning.cmake +++ b/cmake/macros/OptionalWinExecutableSigning.cmake @@ -22,7 +22,7 @@ macro(optional_win_executable_signing) # setup a post build command to sign the executable add_custom_command( TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${SIGNTOOL_EXECUTABLE} sign /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 ${EXECUTABLE_PATH} + COMMAND ${SIGNTOOL_EXECUTABLE} sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 ${EXECUTABLE_PATH} ) else () message(FATAL_ERROR "HF_PFX_PASSPHRASE must be set for executables to be signed.") diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index d8532aa081..76f6812921 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -69,6 +69,8 @@ macro(SET_PACKAGING_PARAMETERS) set(CONSOLE_APP_CONTENTS "${CONSOLE_INSTALL_APP_PATH}/Contents") set(COMPONENT_APP_PATH "${CONSOLE_APP_CONTENTS}/MacOS/Components.app") set(COMPONENT_INSTALL_DIR "${COMPONENT_APP_PATH}/Contents/MacOS") + set(CONSOLE_PLUGIN_INSTALL_DIR "${COMPONENT_APP_PATH}/Contents/PlugIns") + set(INTERFACE_INSTALL_APP_PATH "${CONSOLE_INSTALL_DIR}/${INTERFACE_BUNDLE_NAME}.app") set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.icns") diff --git a/cmake/macros/SetupHifiClientServerPlugin.cmake b/cmake/macros/SetupHifiClientServerPlugin.cmake new file mode 100644 index 0000000000..0ce7796756 --- /dev/null +++ b/cmake/macros/SetupHifiClientServerPlugin.cmake @@ -0,0 +1,61 @@ +# +# Created by Brad Hefta-Gaub on 2016/07/07 +# Copyright 2016 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 +# +macro(SETUP_HIFI_CLIENT_SERVER_PLUGIN) + set(${TARGET_NAME}_SHARED 1) + setup_hifi_library(${ARGV}) + if (NOT DEFINED SERVER_ONLY) + add_dependencies(interface ${TARGET_NAME}) + endif() + set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Plugins") + + if (APPLE) + set(CLIENT_PLUGIN_PATH "${INTERFACE_BUNDLE_NAME}.app/Contents/PlugIns") + set(SERVER_PLUGIN_PATH "plugins") + else() + set(CLIENT_PLUGIN_PATH "plugins") + set(SERVER_PLUGIN_PATH "plugins") + endif() + + if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_GENERATOR STREQUAL "Unix Makefiles") + set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/${CLIENT_PLUGIN_PATH}/") + set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/${SERVER_PLUGIN_PATH}/") + elseif (APPLE) + set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$/${CLIENT_PLUGIN_PATH}/") + set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$/${SERVER_PLUGIN_PATH}/") + else() + set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$/${CLIENT_PLUGIN_PATH}/") + set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$/${SERVER_PLUGIN_PATH}/") + endif() + + # create the destination for the client plugin binaries + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory + ${CLIENT_PLUGIN_FULL_PATH} + ) + # copy the client plugin binaries + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "$" + ${CLIENT_PLUGIN_FULL_PATH} + ) + + # create the destination for the server plugin binaries + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory + ${SERVER_PLUGIN_FULL_PATH} + ) + # copy the server plugin binaries + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "$" + ${SERVER_PLUGIN_FULL_PATH} + ) + +endmacro() diff --git a/cmake/macros/SetupHifiLibrary.cmake b/cmake/macros/SetupHifiLibrary.cmake index 628f65b278..26c769c6e6 100644 --- a/cmake/macros/SetupHifiLibrary.cmake +++ b/cmake/macros/SetupHifiLibrary.cmake @@ -24,6 +24,16 @@ macro(SETUP_HIFI_LIBRARY) set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS -mavx) endif() endforeach() + + # add compiler flags to AVX2 source files + file(GLOB_RECURSE AVX2_SRCS "src/avx2/*.cpp" "src/avx2/*.c") + foreach(SRC ${AVX2_SRCS}) + if (WIN32) + set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS /arch:AVX2) + elseif (APPLE OR UNIX) + set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS "-mavx2 -mfma") + endif() + endforeach() setup_memory_debugger() diff --git a/cmake/macros/TargetNsight.cmake b/cmake/macros/TargetNsight.cmake index 09b056d07a..44ca4eecbf 100644 --- a/cmake/macros/TargetNsight.cmake +++ b/cmake/macros/TargetNsight.cmake @@ -7,18 +7,21 @@ # macro(TARGET_NSIGHT) if (WIN32 AND USE_NSIGHT) - + # grab the global CHECKED_FOR_NSIGHT_ONCE property - get_property(NSIGHT_CHECKED GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE) + get_property(NSIGHT_UNAVAILABLE GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE) - if (NOT NSIGHT_CHECKED) + if (NOT NSIGHT_UNAVAILABLE) # try to find the Nsight package and add it to the build if we find it find_package(NSIGHT) - # set the global CHECKED_FOR_NSIGHT_ONCE property so that we only debug that we couldn't find it once - set_property(GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE TRUE) + # Cache the failure to find nsight, so that we don't check over and over + if (NOT NSIGHT_FOUND) + set_property(GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE TRUE) + endif() endif () - + + # try to find the Nsight package and add it to the build if we find it if (NSIGHT_FOUND) include_directories(${NSIGHT_INCLUDE_DIRS}) add_definitions(-DNSIGHT_FOUND) diff --git a/cmake/macros/TargetSixense.cmake b/cmake/macros/TargetSixense.cmake index 6fd9cede1f..07dcfe67e4 100644 --- a/cmake/macros/TargetSixense.cmake +++ b/cmake/macros/TargetSixense.cmake @@ -6,9 +6,11 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_SIXENSE) - add_dependency_external_projects(sixense) - find_package(Sixense REQUIRED) - target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) - add_definitions(-DHAVE_SIXENSE) + if(NOT APPLE) + add_dependency_external_projects(sixense) + find_package(Sixense REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) + add_definitions(-DHAVE_SIXENSE) + endif() endmacro() diff --git a/cmake/macros/TargetSteamworks.cmake b/cmake/macros/TargetSteamworks.cmake new file mode 100644 index 0000000000..67145050c2 --- /dev/null +++ b/cmake/macros/TargetSteamworks.cmake @@ -0,0 +1,13 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Clement Brisset on 6/8/2016 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_STEAMWORKS) + add_dependency_external_projects(steamworks) + find_package(Steamworks REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${STEAMWORKS_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${STEAMWORKS_LIBRARIES}) +endmacro() diff --git a/cmake/modules/FindSteamworks.cmake b/cmake/modules/FindSteamworks.cmake new file mode 100644 index 0000000000..515a9d4374 --- /dev/null +++ b/cmake/modules/FindSteamworks.cmake @@ -0,0 +1,29 @@ +# +# FindSteamworks.cmake +# +# Try to find the Steamworks controller library +# +# This module defines the following variables +# +# STEAMWORKS_FOUND - Was Steamworks found +# STEAMWORKS_INCLUDE_DIRS - the Steamworks include directory +# STEAMWORKS_LIBRARIES - Link this to use Steamworks +# +# This module accepts the following variables +# +# STEAMWORKS_ROOT - Can be set to steamworks install path or Windows build path +# +# Created on 6/8/2016 by Clement Brisset +# Copyright 2016 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(SelectLibraryConfigurations) +select_library_configurations(STEAMWORKS) + +set(STEAMWORKS_REQUIREMENTS STEAMWORKS_INCLUDE_DIRS STEAMWORKS_LIBRARIES) +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Steamworks DEFAULT_MSG STEAMWORKS_INCLUDE_DIRS STEAMWORKS_LIBRARIES) +mark_as_advanced(STEAMWORKS_LIBRARIES STEAMWORKS_INCLUDE_DIRS STEAMWORKS_SEARCH_DIRS) diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 0ea1199c09..4786b12743 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -64,7 +64,7 @@ ; The Inner invocation has written an uninstaller binary for us. ; We need to sign it if it's a production or PR build. !if @PRODUCTION_BUILD@ == 1 - !system '"@SIGNTOOL_EXECUTABLE@" sign /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0 + !system '"@SIGNTOOL_EXECUTABLE@" sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0 !endif ; Good. Now we can carry on writing the real installer. diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 53d062d4bd..e1334ee46f 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 1.2, + "version": 1.5, "settings": [ { "name": "metaverse", @@ -56,6 +56,7 @@ "label": "Paths", "help": "Clients can enter a path to reach an exact viewpoint in your domain.
Add rows to the table below to map a path to a viewpoint.
The index path ( / ) is where clients will enter if they do not enter an explicit path.", "type": "table", + "can_add_new_rows": true, "key": { "name": "path", "label": "Path", @@ -71,6 +72,290 @@ } ] }, + { + "name": "descriptors", + "label": "Description", + "help": "This data will be queryable from your server. It may be collected by High Fidelity and used to share your domain with others.", + "settings": [ + { + "name": "description", + "label": "Description", + "help": "A description of your domain (256 character limit)." + }, + { + "name": "maturity", + "label": "Maturity", + "help": "A maturity rating, available as a guideline for content on your domain.", + "default": "unrated", + "type": "select", + "options": [ + { + "value": "unrated", + "label": "Unrated" + }, + { + "value": "everyone", + "label": "Everyone" + }, + { + "value": "teen", + "label": "Teen (13+)" + }, + { + "value": "mature", + "label": "Mature (17+)" + }, + { + "value": "adult", + "label": "Adult (18+)" + } + ] + + }, + { + "name": "hosts", + "label": "Hosts", + "type": "table", + "can_add_new_rows": true, + "help": "Usernames of hosts who can reliably show your domain to new visitors.", + "numbered": false, + "columns": [ + { + "name": "host", + "label": "Username", + "can_set": true + } + ] + }, + { + "name": "tags", + "label": "Tags", + "type": "table", + "can_add_new_rows": true, + "help": "Common categories under which your domain falls.", + "numbered": false, + "columns": [ + { + "name": "tag", + "label": "Tag", + "can_set": true + } + ] + }, + { + "label": "Operating Hours", + "help": "\"Open\" domains can be searched using their operating hours. Hours are entered in the local timezone, selected below.", + + "name": "weekday_hours", + "caption": "Weekday Hours (Monday-Friday)", + "type": "table", + "can_add_new_rows": false, + "columns": [ + { + "name": "open", + "label": "Opening Time", + "type": "time", + "default": "00:00", + "editable": true + }, + { + "name": "close", + "label": "Closing Time", + "type": "time", + "default": "23:59", + "editable": true + } + ] + }, + { + "name": "weekend_hours", + "label": "Weekend Hours (Saturday/Sunday)", + "type": "table", + "can_add_new_rows": false, + "columns": [ + { + "name": "open", + "label": "Opening Time", + "type": "time", + "default": "00:00", + "editable": true + }, + { + "name": "close", + "label": "Closing Time", + "type": "time", + "default": "23:59", + "editable": true + } + ] + }, + { + "label": "Time Zone", + "name": "utc_offset", + "caption": "Time Zone", + "help": "This server's time zone. Used to define your server's operating hours.", + "type": "select", + "options": [ + { + "value": "-12", + "label": "UTC-12:00" + }, + { + "value": "-11", + "label": "UTC-11:00" + }, + { + "value": "-10", + "label": "UTC-10:00" + }, + { + "value": "-9.5", + "label": "UTC-09:30" + }, + { + "value": "-9", + "label": "UTC-09:00" + }, + { + "value": "-8", + "label": "UTC-08:00" + }, + { + "value": "-7", + "label": "UTC-07:00" + }, + { + "value": "-6", + "label": "UTC-06:00" + }, + { + "value": "-5", + "label": "UTC-05:00" + }, + { + "value": "-4", + "label": "UTC-04:00" + }, + { + "value": "-3.5", + "label": "UTC-03:30" + }, + { + "value": "-3", + "label": "UTC-03:00" + }, + { + "value": "-2", + "label": "UTC-02:00" + }, + { + "value": "-1", + "label": "UTC-01:00" + }, + { + "value": "", + "label": "UTC±00:00" + }, + { + "value": "1", + "label": "UTC+01:00" + }, + { + "value": "2", + "label": "UTC+02:00" + }, + { + "value": "3", + "label": "UTC+03:00" + }, + { + "value": "3.5", + "label": "UTC+03:30" + }, + { + "value": "4", + "label": "UTC+04:00" + }, + { + "value": "4.5", + "label": "UTC+04:30" + }, + { + "value": "5", + "label": "UTC+05:00" + }, + { + "value": "5.5", + "label": "UTC+05:30" + }, + { + "value": "5.75", + "label": "UTC+05:45" + }, + { + "value": "6", + "label": "UTC+06:00" + }, + { + "value": "6.5", + "label": "UTC+06:30" + }, + { + "value": "7", + "label": "UTC+07:00" + }, + { + "value": "8", + "label": "UTC+08:00" + }, + { + "value": "8.5", + "label": "UTC+08:30" + }, + { + "value": "8.75", + "label": "UTC+08:45" + }, + { + "value": "9", + "label": "UTC+09:00" + }, + { + "value": "9.5", + "label": "UTC+09:30" + }, + { + "value": "10", + "label": "UTC+10:00" + }, + { + "value": "10.5", + "label": "UTC+10:30" + }, + { + "value": "11", + "label": "UTC+11:00" + }, + { + "value": "12", + "label": "UTC+12:00" + }, + { + "value": "12.75", + "label": "UTC+12:45" + }, + { + "value": "13", + "label": "UTC+13:00" + }, + { + "value": "14", + "label": "UTC+14:00" + } + ] + } + ] + }, { "name": "security", "label": "Security", @@ -87,55 +372,150 @@ "help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.", "value-hidden": true }, - { - "name": "restricted_access", - "type": "checkbox", - "label": "Restricted Access", - "default": false, - "help": "Only users listed in \"Allowed Users\" can enter your domain." - }, - { - "name": "allowed_users", - "type": "table", - "label": "Allowed Users", - "help": "You can always connect from the domain-server machine.", - "numbered": false, - "columns": [ - { - "name": "username", - "label": "Username", - "can_set": true - } - ] - }, { "name": "maximum_user_capacity", "label": "Maximum User Capacity", - "help": "The limit on how many avatars can be connected at once. 0 means no limit.", + "help": "The limit on how many users can be connected at once (0 means no limit). Avatars connected from the same machine will not count towards this limit.", "placeholder": "0", "default": "0", "advanced": false }, { - "name": "allowed_editors", + "name": "standard_permissions", "type": "table", - "label": "Allowed Editors", - "help": "List the High Fidelity names for people you want to be able lock or unlock entities in this domain.
An empty list means everyone.", - "numbered": false, + "label": "Domain-Wide User Permissions", + "help": "Indicate which users or groups can have which domain-wide permissions.", + "caption": "Standard Permissions", + "can_add_new_rows": false, + + "groups": [ + { + "label": "User / Group", + "span": 1 + }, + { + "label": "Permissions ?", + "span": 6 + } + ], + "columns": [ { - "name": "username", - "label": "Username", - "can_set": true + "name": "permissions_id", + "label": "" + }, + { + "name": "id_can_connect", + "label": "Connect", + "type": "checkbox", + "editable": true, + "default": true + }, + { + "name": "id_can_adjust_locks", + "label": "Lock / Unlock", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez", + "label": "Rez", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp", + "label": "Rez Temporary", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_write_to_asset_server", + "label": "Write Assets", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_connect_past_max_capacity", + "label": "Ignore Max Capacity", + "type": "checkbox", + "editable": true, + "default": false } - ] + ], + + "non-deletable-row-key": "permissions_id", + "non-deletable-row-values": ["localhost", "anonymous", "logged-in"] }, { - "name": "editors_are_rezzers", - "type": "checkbox", - "label": "Only Editors Can Create Entities", - "help": "Only users listed in \"Allowed Editors\" can create new entites.", - "default": false + "name": "permissions", + "type": "table", + "caption": "Permissions for Specific Users", + "can_add_new_rows": true, + + "groups": [ + { + "label": "User / Group", + "span": 1 + }, + { + "label": "Permissions ?", + "span": 6 + } + ], + + "columns": [ + { + "name": "permissions_id", + "label": "" + }, + { + "name": "id_can_connect", + "label": "Connect", + "type": "checkbox", + "editable": true, + "default": true + }, + { + "name": "id_can_adjust_locks", + "label": "Lock / Unlock", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez", + "label": "Rez", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp", + "label": "Rez Temporary", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_write_to_asset_server", + "label": "Write Assets", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_connect_past_max_capacity", + "label": "Ignore Max Capacity", + "type": "checkbox", + "editable": true, + "default": false + } + ] } ] }, @@ -148,6 +528,8 @@ "type": "table", "label": "Persistent Scripts", "help": "Add the URLs for scripts that you would like to ensure are always running in your domain.", + "can_add_new_rows": true, + "columns": [ { "name": "url", @@ -207,8 +589,8 @@ "name": "attenuation_per_doubling_in_distance", "label": "Default Domain Attenuation", "help": "Factor between 0 and 1.0 (0: No attenuation, 1.0: extreme attenuation)", - "placeholder": "0.18", - "default": "0.18", + "placeholder": "0.5", + "default": "0.5", "advanced": false }, { @@ -232,6 +614,8 @@ "label": "Zones", "help": "In this table you can define a set of zones in which you can specify various audio properties.", "numbered": false, + "can_add_new_rows": true, + "key": { "name": "name", "label": "Name", @@ -283,6 +667,8 @@ "help": "In this table you can set custom attenuation coefficients between audio zones", "numbered": true, "can_order": true, + "can_add_new_rows": true, + "columns": [ { "name": "source", @@ -300,7 +686,7 @@ "name": "coefficient", "label": "Attenuation coefficient", "can_set": true, - "placeholder": "0.18" + "placeholder": "0.5" } ] }, @@ -310,6 +696,8 @@ "label": "Reverb Settings", "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.", "numbered": true, + "can_add_new_rows": true, + "columns": [ { "name": "zone", @@ -330,6 +718,14 @@ "placeholder": "(in percent)" } ] + }, + { + "name": "codec_preference_order", + "label": "Audio Codec Preference Order", + "help": "List of codec names in order of preferred usage", + "placeholder": "hifiAC, zlib, pcm", + "default": "hifiAC,zlib,pcm", + "advanced": true } ] }, @@ -409,6 +805,14 @@ "label": "Entity Server Settings", "assignment-types": [6], "settings": [ + { + "name": "maxTmpLifetime", + "label": "Maximum Lifetime of Temporary Entities", + "help": "The maximum number of seconds for the lifetime of an entity which will be considered \"temporary\".", + "placeholder": "3600", + "default": "3600", + "advanced": true + }, { "name": "persistFilePath", "label": "Entities File Path", @@ -431,6 +835,8 @@ "label": "Backup Rules", "help": "In this table you can define a set of rules for how frequently to backup copies of your entites content file.", "numbered": false, + "can_add_new_rows": true, + "default": [ {"Name":"Half Hourly Rolling","backupInterval":1800,"format":".backup.halfhourly.%N","maxBackupVersions":5}, {"Name":"Daily Rolling","backupInterval":86400,"format":".backup.daily.%N","maxBackupVersions":7}, diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index efb9e907c5..2862feed87 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -20,6 +20,17 @@ body { top: 40px; } +.table .value-row td, .table .inputs td { + vertical-align: middle; +} + +.table .table-checkbox { + /* Fix IE sizing checkboxes to fill table cell */ + width: auto; + margin-left: auto; + margin-right: auto; +} + .glyphicon-remove { font-size: 24px; } @@ -107,6 +118,58 @@ table { word-wrap: break-word; } +caption { + color: #333; + font-weight: 700; + padding-top: 0; +} + +table > tbody > .headers > td { + vertical-align: middle; +} + +table .headers + .headers td { + font-size: 13px; + color: #222; +} + +table[name="security.standard_permissions"] .headers td + td, table[name="security.permissions"] .headers td + td { + text-align: center; +} + +.tooltip.top .tooltip-arrow { + border-top-color: #fff; + border-width: 10px 10px 0; + margin-bottom: -5px; +} + +.tooltip-inner { + padding: 20px 20px 10px 20px; + font-size: 14px; + text-align: left; + color: #333; + background-color: #fff; + box-shadow: 0 3px 8px 8px #e8e8e8; +} + +.tooltip.in { + opacity: 1; +} + +.tooltip-inner ul { + padding-left: 0; + margin-bottom: 15px; +} + +.tooltip-inner li { + list-style-type: none; + margin-bottom: 5px; +} + +#security .tooltip-inner { + max-width: 520px; +} + #xs-advanced-container { margin-bottom: 20px; } diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index e17a886e10..4f153d6190 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -232,6 +232,27 @@ $(document).ready(function(){ badgeSidebarForDifferences($(this)); }); + // Bootstrap switch in table + $('#' + Settings.FORM_ID).on('change', 'input.table-checkbox', function () { + // Bootstrap switches in table: set the changed data attribute for all rows in table. + var row = $(this).closest('tr'); + if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. + row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); + updateDataChangedForSiblingRows(row, true); + badgeSidebarForDifferences($(this)); + } + }); + + $('#' + Settings.FORM_ID).on('change', 'input.table-time', function() { + // Bootstrap switches in table: set the changed data attribute for all rows in table. + var row = $(this).closest('tr'); + if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. + row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); + updateDataChangedForSiblingRows(row, true); + badgeSidebarForDifferences($(this)); + } + }); + $('.advanced-toggle').click(function(){ Settings.showAdvanced = !Settings.showAdvanced var advancedSelector = $('.' + Settings.ADVANCED_CLASS) @@ -436,6 +457,8 @@ function disonnectHighFidelityAccount() { }, function(){ // we need to post to settings to clear the access-token $(Settings.ACCESS_TOKEN_SELECTOR).val('').change(); + // reset the domain id to get a new temporary name + $(Settings.DOMAIN_ID_SELECTOR).val('').change(); saveSettings(); }); } @@ -534,7 +557,7 @@ function createNewDomainID(description, justConnected) { // get the JSON object ready that we'll use to create a new domain var domainJSON = { "domain": { - "description": description + "private_description": description }, "access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val() } @@ -727,8 +750,8 @@ function chooseFromHighFidelityDomains(clickedButton) { _.each(data.data.domains, function(domain){ var domainString = ""; - if (domain.description) { - domainString += '"' + domain.description + '" - '; + if (domain.private_description) { + domainString += '"' + domain.private_description + '" - '; } domainString += domain.id; @@ -841,6 +864,8 @@ function reloadSettings(callback) { // setup any bootstrap switches $('.toggle-checkbox').bootstrapSwitch(); + $('[data-toggle="tooltip"]').tooltip(); + // add tooltip to locked settings $('label.locked').tooltip({ placement: 'right', @@ -875,6 +900,7 @@ function saveSettings() { } } + console.log("----- SAVING ------"); console.log(formJSON); // re-enable all inputs @@ -908,10 +934,33 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" + setting.help + "" } + var nonDeletableRowKey = setting["non-deletable-row-key"]; + var nonDeletableRowValues = setting["non-deletable-row-values"]; + html += ""; + if (setting.caption) { + html += "" + } + + // Column groups + if (setting.groups) { + html += "" + _.each(setting.groups, function (group) { + html += "" + }) + if (!isLocked && !setting.read_only) { + if (setting.can_order) { + html += ""; + } + html += "" + } + html += "" + } + // Column names html += "" @@ -950,6 +999,8 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" } + var isNonDeletableRow = !setting.can_add_new_rows; + _.each(setting.columns, function(col) { if (isArray) { @@ -961,16 +1012,23 @@ function makeTable(setting, keypath, setting_value, isLocked) { colName = keypath + "." + rowIndexOrName + "." + col.name; } - // setup the td for this column - html += ""; + } else if (isArray && col.type === "time" && col.editable) { + html += ""; + } else { + // Use a hidden input so that the values are posted. + html += ""; + } - // for values to be posted properly we add a hidden input to this td - html += ""; - - html += ""; }) if (!isLocked && !setting.read_only) { @@ -979,8 +1037,12 @@ function makeTable(setting, keypath, setting_value, isLocked) { "'>" + "" } - html += "" + if (isNonDeletableRow) { + html += ""; + } else { + html += ""; + } } html += "" @@ -990,7 +1052,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { } // populate inputs in the table for new values - if (!isLocked && !setting.read_only) { + if (!isLocked && !setting.read_only && setting.can_add_new_rows) { html += makeTableInputs(setting) } html += "
" + setting.caption + "
" + group.label + "
" + rowIndexOrName + ""; + isNonDeletableRow = isNonDeletableRow + || (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1); - // add the actual value to the td so it is displayed - html += colValue; + if (isArray && col.type === "checkbox" && col.editable) { + html += "" + + "" + + "" + + colValue + "
" @@ -1012,17 +1074,23 @@ function makeTableInputs(setting) { } _.each(setting.columns, function(col) { - html += "\ - \ - " + if (col.type === "checkbox") { + html += "" + + ""; + } else { + html += "\ + \ + " + } }) if (setting.can_order) { html += "" } - html += "" + html += "" html += "" return html @@ -1127,11 +1195,11 @@ function addTableRow(add_glyphicon) { } else { $(element).html(1) } - } else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) { - $(element).html("") - } else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { + } else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) { + $(element).html("") + } else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { // Change buttons var anchor = $(element).children("a") anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES) @@ -1142,8 +1210,26 @@ function addTableRow(add_glyphicon) { input.remove() } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) { // Hide inputs - var input = $(element).children("input") - input.attr("type", "hidden") + var input = $(element).find("input") + var isCheckbox = false; + var isTime = false; + if (input.hasClass("table-checkbox")) { + input = $(input).parent(); + isCheckbox = true; + } else if (input.hasClass("table-time")) { + input = $(input).parent(); + isTime = true; + } + + var val = input.val(); + if (isCheckbox) { + // don't hide the checkbox + val = $(input).find("input").is(':checked'); + } else if (isTime) { + // don't hide the time + } else { + input.attr("type", "hidden") + } if (isArray) { var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length @@ -1152,14 +1238,22 @@ function addTableRow(add_glyphicon) { // are there multiple columns or just one? // with multiple we have an array of Objects, with one we have an array of whatever the value type is var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length - input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) + + if (isCheckbox) { + $(input).find("input").attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) + } else { + input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) + } } else { input.attr("name", full_name + "." + $(element).attr("name")) } - input.attr("data-changed", "true") - - $(element).append(input.val()) + if (isCheckbox) { + $(input).find("input").attr("data-changed", "true"); + } else { + input.attr("data-changed", "true"); + $(element).append(val); + } } else { console.log("Unknown table element") } diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 94e79416a5..c4a7d1a425 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -26,7 +26,7 @@ using SharedAssignmentPointer = QSharedPointer; DomainGatekeeper::DomainGatekeeper(DomainServer* server) : _server(server) { - + } void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID, @@ -38,7 +38,7 @@ void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID) { auto it = _pendingAssignedNodes.find(tempUUID); - + if (it != _pendingAssignedNodes.end()) { return it->second.getAssignmentUUID(); } else { @@ -55,57 +55,63 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSize() == 0) { return; } - 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, message->getSenderSockAddr()); - + + QByteArray myProtocolVersion = protocolVersionsSignature(); + if (nodeConnection.protocolVersion != myProtocolVersion) { + sendProtocolMismatchConnectionDenial(message->getSenderSockAddr()); + return; + } + if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) { qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection."; return; } - + static const NodeSet VALID_NODE_TYPES { NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent, NodeType::MessagesMixer }; - + if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) { qDebug() << "Received an invalid node type with connect request. Will not allow connection from" << nodeConnection.senderSockAddr << ": " << nodeConnection.nodeType; return; } - + // check if this connect request matches an assignment in the queue auto pendingAssignment = _pendingAssignedNodes.find(nodeConnection.connectUUID); - + SharedNodePointer node; - + if (pendingAssignment != _pendingAssignedNodes.end()) { node = processAssignmentConnectRequest(nodeConnection, pendingAssignment->second); } else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) { QString username; QByteArray usernameSignature; - + if (message->getBytesLeftToRead() > 0) { // read username from packet packetStream >> username; - + if (message->getBytesLeftToRead() > 0) { // read user signature from packet packetStream >> usernameSignature; } } - + node = processAgentConnectRequest(nodeConnection, username, usernameSignature); } - + if (node) { // set the sending sock addr and node interest set on this node DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); nodeData->setSendingSockAddr(message->getSenderSockAddr()); nodeData->setNodeInterestSet(nodeConnection.interestList.toSet()); - + nodeData->setPlaceName(nodeConnection.placeName); + // signal that we just connected a node so the DomainServer can get it a list // and broadcast its presence right away emit connectedNode(node); @@ -114,18 +120,72 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer nodesToKill; + + auto limitedNodeList = DependencyManager::get(); + limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){ + QString username = node->getPermissions().getUserName(); + NodePermissions userPerms(username); + + if (node->getPermissions().isAssignment) { + // this node is an assignment-client + userPerms.isAssignment = true; + userPerms.canAdjustLocks = true; + userPerms.canRezPermanentEntities = true; + userPerms.canRezTemporaryEntities = true; + } else { + // this node is an agent + userPerms.setAll(false); + + const QHostAddress& addr = node->getLocalSocket().getAddress(); + bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() || + addr == QHostAddress::LocalHost); + if (isLocalUser) { + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost); + } + + if (username.isEmpty()) { + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); + } else { + if (_server->_settingsManager.havePermissionsForName(username)) { + userPerms = _server->_settingsManager.getPermissionsForName(username); + } else { + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); + } + } + } + + node->setPermissions(userPerms); + + if (!userPerms.canConnectToDomain) { + qDebug() << "node" << node->getUUID() << "no longer has permission to connect."; + // hang up on this node + nodesToKill << node; + } + }); + + foreach (auto node, nodesToKill) { + emit killNode(node); + } +} + SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection, const PendingAssignedNodeData& pendingAssignment) { - + // make sure this matches an assignment the DS told us we sent out auto it = _pendingAssignedNodes.find(nodeConnection.connectUUID); - + SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer(); - + if (it != _pendingAssignedNodes.end()) { // find the matching queued static assignment in DS queue matchingQueuedAssignment = _server->dequeueMatchingAssignment(it->second.getAssignmentUUID(), nodeConnection.nodeType); - + if (matchingQueuedAssignment) { qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID) << "matches unfulfilled assignment" @@ -140,123 +200,99 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo qDebug() << "No assignment was deployed with UUID" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID); return SharedNodePointer(); } - + // add the new node SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection); - + DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); - + // set assignment related data on the linked data for this node nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID()); nodeData->setWalletUUID(it->second.getWalletUUID()); nodeData->setNodeVersion(it->second.getNodeVersion()); - + nodeData->setWasAssigned(true); + // cleanup the PendingAssignedNodeData for this assignment now that it's connecting _pendingAssignedNodes.erase(it); - + // always allow assignment clients to create and destroy entities - newNode->setIsAllowedEditor(true); - newNode->setCanRez(true); - + NodePermissions userPerms; + userPerms.isAssignment = true; + userPerms.canAdjustLocks = true; + userPerms.canRezPermanentEntities = true; + userPerms.canRezTemporaryEntities = true; + newNode->setPermissions(userPerms); return newNode; } const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; -const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; -const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection, const QString& username, const QByteArray& usernameSignature) { - + auto limitedNodeList = DependencyManager::get(); - - bool isRestrictingAccess = - _server->_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); - - // check if this user is on our local machine - if this is true they are always allowed to connect + + // start with empty permissions + NodePermissions userPerms(username); + userPerms.setAll(false); + + // check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress(); bool isLocalUser = (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); - - // if we're using restricted access and this user is not local make sure we got a user signature - if (isRestrictingAccess && !isLocalUser) { - if (!username.isEmpty()) { - if (usernameSignature.isEmpty()) { - // if user didn't include usernameSignature in connect request, send a connectionToken packet - sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); - - // ask for their public key right now to make sure we have it - requestUserPublicKey(username); - - return SharedNodePointer(); - } - } + if (isLocalUser) { + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost); + qDebug() << "user-permissions: is local user, so:" << userPerms; } - - bool verifiedUsername = false; - - // if we do not have a local user we need to subject them to our verification and capacity checks - if (!isLocalUser) { - - // check if we need to look at the username signature - if (isRestrictingAccess) { - if (isVerifiedAllowedUser(username, usernameSignature, nodeConnection.senderSockAddr)) { - // we verified the user via their username and signature - set the verifiedUsername - // so we don't re-decrypt their sig if we're trying to exempt them from max capacity check (due to - // being in the allowed editors list) - verifiedUsername = true; - } else { - // failed to verify user - return a null shared ptr - return SharedNodePointer(); - } - } - - if (!isWithinMaxCapacity(username, usernameSignature, verifiedUsername, nodeConnection.senderSockAddr)) { - // we can't allow this user to connect because we are at max capacity (and they either aren't an allowed editor - // or couldn't be verified as one) + + if (!username.isEmpty() && usernameSignature.isEmpty()) { + // user is attempting to prove their identity to us, but we don't have enough information + sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); + // ask for their public key right now to make sure we have it + requestUserPublicKey(username); + if (!userPerms.canConnectToDomain) { return SharedNodePointer(); } } - - // if this user is in the editors list (or if the editors list is empty) set the user's node's isAllowedEditor to true - const QVariant* allowedEditorsVariant = - valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); - QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); - - // if the allowed editors list is empty then everyone can adjust locks - bool isAllowedEditor = allowedEditors.empty(); - - if (allowedEditors.contains(username, Qt::CaseInsensitive)) { - // we have a non-empty allowed editors list - check if this user is verified to be in it - if (!verifiedUsername) { - if (!verifyUserSignature(username, usernameSignature, HifiSockAddr())) { - // failed to verify a user that is in the allowed editors list - - // TODO: fix public key refresh in interface/metaverse and force this check - qDebug() << "Could not verify user" << username << "as allowed editor. In the interim this user" - << "will be given edit rights to avoid a thrasing of public key requests and connect requests."; - } - - isAllowedEditor = true; + + if (username.isEmpty()) { + // they didn't tell us who they are + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); + qDebug() << "user-permissions: no username, so:" << userPerms; + } else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { + // they are sent us a username and the signature verifies it + if (_server->_settingsManager.havePermissionsForName(username)) { + // we have specific permissions for this user. + userPerms = _server->_settingsManager.getPermissionsForName(username); + qDebug() << "user-permissions: specific user matches, so:" << userPerms; } else { - // already verified this user and they are in the allowed editors list - isAllowedEditor = true; + // they are logged into metaverse, but we don't have specific permissions for them. + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); + qDebug() << "user-permissions: user is logged in, so:" << userPerms; + } + userPerms.setUserName(username); + } else { + // they sent us a username, but it didn't check out + requestUserPublicKey(username); + if (!userPerms.canConnectToDomain) { + return SharedNodePointer(); } } - - // check if only editors should be able to rez entities - const QVariant* editorsAreRezzersVariant = - valueForKeyPath(_server->_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH); - - bool onlyEditorsAreRezzers = false; - if (editorsAreRezzersVariant) { - onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool(); + + qDebug() << "user-permissions: final:" << userPerms; + + if (!userPerms.canConnectToDomain) { + sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", + nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers); + return SharedNodePointer(); } - - bool canRez = true; - if (onlyEditorsAreRezzers) { - canRez = isAllowedEditor; + + if (!userPerms.canConnectPastMaxCapacity && !isWithinMaxCapacity()) { + // we can't allow this user to connect because we are at max capacity + sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr, + DomainHandler::ConnectionRefusedReason::TooManyUsers); + return SharedNodePointer(); } QUuid hintNodeID; @@ -275,24 +311,23 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect return true; }); - + // add the connecting node (or re-use the matched one from eachNodeBreakable above) SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID); - + // set the edit rights for this user - newNode->setIsAllowedEditor(isAllowedEditor); - newNode->setCanRez(canRez); - + newNode->setPermissions(userPerms); + // grab the linked data for our new node so we can set the username DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); - + // if we have a username from the connect request, set it on the DomainServerNodeData nodeData->setUsername(username); - + // also add an interpolation to DomainServerNodeData so that servers can get username in stats nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY, uuidStringWithoutCurlyBraces(newNode->getUUID()), username); - + return newNode; } @@ -300,11 +335,11 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node QUuid nodeID) { HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr; SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID); - + if (connectedPeer) { // this user negotiated a connection with us via ICE, so re-use their ICE client ID nodeID = nodeConnection.connectUUID; - + if (connectedPeer->getActiveSocket()) { // set their discovered socket to whatever the activated socket on the network peer object was discoveredSocket = *connectedPeer->getActiveSocket(); @@ -315,39 +350,39 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node nodeID = QUuid::createUuid(); } } - + auto limitedNodeList = DependencyManager::get(); - + SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeID, nodeConnection.nodeType, nodeConnection.publicSockAddr, nodeConnection.localSockAddr); - + // So that we can send messages to this node at will - we need to activate the correct socket on this node now newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket); - + return newNode; } bool DomainGatekeeper::verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr) { - + // it's possible this user can be allowed to connect, but we need to check their username signature QByteArray publicKeyArray = _userPublicKeys.value(username); - + const QUuid& connectionToken = _connectionTokenHash.value(username.toLower()); - + if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) { // if we do have a public key for the user, check for a signature match - + const unsigned char* publicKeyData = reinterpret_cast(publicKeyArray.constData()); - + // first load up the public key into an RSA struct RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size()); - + QByteArray lowercaseUsername = username.toLower().toUtf8(); QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), QCryptographicHash::Sha256); - + if (rsaPublicKey) { int decryptResult = RSA_verify(NID_sha256, reinterpret_cast(usernameWithToken.constData()), @@ -355,117 +390,72 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, reinterpret_cast(usernameSignature.constData()), usernameSignature.size(), rsaPublicKey); - + if (decryptResult == 1) { - qDebug() << "Username signature matches for" << username << "- allowing connection."; - + qDebug() << "Username signature matches for" << username; + // free up the public key and remove connection token before we return RSA_free(rsaPublicKey); _connectionTokenHash.remove(username); - + return true; - + } else { if (!senderSockAddr.isNull()) { qDebug() << "Error decrypting username signature for " << username << "- denying connection."; - sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr); + sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } - + // free up the public key, we don't need it anymore RSA_free(rsaPublicKey); } - + } else { - + // we can't let this user in since we couldn't convert their public key to an RSA key we could use if (!senderSockAddr.isNull()) { qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection."; - sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr); + sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } } } else { if (!senderSockAddr.isNull()) { qDebug() << "Insufficient data to decrypt username signature - denying connection."; - sendConnectionDeniedPacket("Insufficient data", senderSockAddr); + sendConnectionDeniedPacket("Insufficient data", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } } - + requestUserPublicKey(username); // no joy. maybe next time? return false; } -bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature, - const HifiSockAddr& senderSockAddr) { - - if (username.isEmpty()) { - qDebug() << "Connect request denied - no username provided."; - - sendConnectionDeniedPacket("No username provided", senderSockAddr); - - return false; - } - - QStringList allowedUsers = - _server->_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList(); - - if (allowedUsers.contains(username, Qt::CaseInsensitive)) { - if (!verifyUserSignature(username, usernameSignature, senderSockAddr)) { - return false; - } - } else { - qDebug() << "Connect request denied for user" << username << "- not in allowed users list."; - sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr); - - return false; - } - - return true; -} - -bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature, - bool& verifiedUsername, - const HifiSockAddr& senderSockAddr) { +bool DomainGatekeeper::isWithinMaxCapacity() { // find out what our maximum capacity is - const QVariant* maximumUserCapacityVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); + const QVariant* maximumUserCapacityVariant = + valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0; - + if (maximumUserCapacity > 0) { unsigned int connectedUsers = _server->countConnectedUsers(); - + if (connectedUsers >= maximumUserCapacity) { - // too many users, deny the new connection unless this user is an allowed editor - - const QVariant* allowedEditorsVariant = - valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); - - QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); - if (allowedEditors.contains(username)) { - if (verifiedUsername || verifyUserSignature(username, usernameSignature, senderSockAddr)) { - verifiedUsername = true; - qDebug() << "Above maximum capacity -" << connectedUsers << "/" << maximumUserCapacity << - "but user" << username << "is in allowed editors list so will be allowed to connect."; - return true; - } - } - - // deny connection from this user qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection."; - sendConnectionDeniedPacket("Too many connected users.", senderSockAddr); - return false; } - + qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, allowing new connection."; } - + return true; } void DomainGatekeeper::preloadAllowedUserPublicKeys() { - const QVariant* allowedUsersVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH); - QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList(); - + QStringList allowedUsers = _server->_settingsManager.getAllNames(); + if (allowedUsers.size() > 0) { // in the future we may need to limit how many requests here - for now assume that lists of allowed users are not // going to create > 100 requests @@ -476,58 +466,76 @@ void DomainGatekeeper::preloadAllowedUserPublicKeys() { } void DomainGatekeeper::requestUserPublicKey(const QString& username) { + // don't request public keys for the standard psuedo-account-names + if (NodePermissions::standardNames.contains(username, Qt::CaseInsensitive)) { + return; + } + // even if we have a public key for them right now, request a new one in case it has just changed JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; callbackParams.jsonCallbackMethod = "publicKeyJSONCallback"; - + const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key"; - + qDebug() << "Requesting public key for user" << username; - - AccountManager::getInstance().sendRequest(USER_PUBLIC_KEY_PATH.arg(username), + + DependencyManager::get()->sendRequest(USER_PUBLIC_KEY_PATH.arg(username), AccountManagerAuth::None, QNetworkAccessManager::GetOperation, callbackParams); } void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); - + if (jsonObject["status"].toString() == "success") { // figure out which user this is for - + const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key"; QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING); - + if (usernameRegex.indexIn(requestReply.url().toString()) != -1) { QString username = usernameRegex.cap(1); - + qDebug() << "Storing a public key for user" << username; - + // pull the public key as a QByteArray from this response const QString JSON_DATA_KEY = "data"; const QString JSON_PUBLIC_KEY_KEY = "public_key"; - + _userPublicKeys[username] = QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8()); } } } -void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr) { +void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr) { + QString protocolVersionError = "Protocol version mismatch - Domain version: " + QCoreApplication::applicationVersion(); + + qDebug() << "Protocol Version mismatch - denying connection."; + + sendConnectionDeniedPacket(protocolVersionError, senderSockAddr, + DomainHandler::ConnectionRefusedReason::ProtocolMismatch); +} + +void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, + DomainHandler::ConnectionRefusedReason reasonCode) { // this is an agent and we've decided we won't let them connect - send them a packet to deny connection QByteArray utfString = reason.toUtf8(); quint16 payloadSize = utfString.size(); - + // setup the DomainConnectionDenied packet - auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, payloadSize + sizeof(payloadSize)); - + auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, + payloadSize + sizeof(payloadSize) + sizeof(uint8_t)); + // pack in the reason the connection was denied (the client displays this) if (payloadSize > 0) { + uint8_t reasonCodeWire = (uint8_t)reasonCode; + connectionDeniedPacket->writePrimitive(reasonCodeWire); connectionDeniedPacket->writePrimitive(payloadSize); connectionDeniedPacket->write(utfString); } - + // send the packet off DependencyManager::get()->sendPacket(std::move(connectionDeniedPacket), senderSockAddr); } @@ -535,20 +543,20 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const H void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr) { // get the existing connection token or create a new one QUuid& connectionToken = _connectionTokenHash[username.toLower()]; - + if (connectionToken.isNull()) { connectionToken = QUuid::createUuid(); } - + // setup a static connection token packet static auto connectionTokenPacket = NLPacket::create(PacketType::DomainServerConnectionToken, NUM_BYTES_RFC4122_UUID); - + // reset the packet before each time we send connectionTokenPacket->reset(); - + // write the connection token connectionTokenPacket->write(connectionToken.toRfc4122()); - + // send off the packet unreliably DependencyManager::get()->sendUnreliablePacket(*connectionTokenPacket, senderSockAddr); } @@ -556,33 +564,33 @@ void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS; void DomainGatekeeper::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) { - + if (peer->getConnectionAttempts() >= NUM_PEER_PINGS_BEFORE_DELETE) { // we've reached the maximum number of ping attempts qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID(); qDebug() << "Removing from list of connecting peers."; - + _icePeers.remove(peer->getUUID()); } else { auto limitedNodeList = DependencyManager::get(); - + // send the ping packet to the local and public sockets for this node auto localPingPacket = limitedNodeList->constructICEPingPacket(PingType::Local, limitedNodeList->getSessionUUID()); limitedNodeList->sendPacket(std::move(localPingPacket), peer->getLocalSocket()); - + auto publicPingPacket = limitedNodeList->constructICEPingPacket(PingType::Public, limitedNodeList->getSessionUUID()); limitedNodeList->sendPacket(std::move(publicPingPacket), peer->getPublicSocket()); - + peer->incrementConnectionAttempts(); } } void DomainGatekeeper::handlePeerPingTimeout() { NetworkPeer* senderPeer = qobject_cast(sender()); - + if (senderPeer) { SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID()); - + if (sharedPeer && !sharedPeer->getActiveSocket()) { pingPunchForConnectingPeer(sharedPeer); } @@ -593,24 +601,24 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointergetMessage()); - + NetworkPeer* receivedPeer = new NetworkPeer; iceResponseStream >> *receivedPeer; - + if (!_icePeers.contains(receivedPeer->getUUID())) { qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer; SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer); _icePeers[receivedPeer->getUUID()] = newPeer; - + // make sure we know when we should ping this peer connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout); - + // immediately ping the new peer, and start a timer to continue pinging it until we connect to it newPeer->startPingTimer(); - + qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" << newPeer->getUUID(); - + pingPunchForConnectingPeer(newPeer); } else { delete receivedPeer; @@ -620,18 +628,18 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer message) { auto limitedNodeList = DependencyManager::get(); auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID()); - + limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr()); } void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer message) { QDataStream packetStream(message->getMessage()); - + QUuid nodeUUID; packetStream >> nodeUUID; - + SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID); - + if (sendingPeer) { // we had this NetworkPeer in our connecting list - add the right sock addr to our connected list sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr()); diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index c4ac32fabf..50bbf38543 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -19,6 +19,8 @@ #include #include +#include + #include #include #include @@ -40,6 +42,8 @@ public: void preloadAllowedUserPublicKeys(); void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); } + + static void sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr); public slots: void processConnectRequestPacket(QSharedPointer message); void processICEPingPacket(QSharedPointer message); @@ -49,8 +53,12 @@ public slots: void publicKeyJSONCallback(QNetworkReply& requestReply); signals: + void killNode(SharedNodePointer node); void connectedNode(SharedNodePointer node); - + +public slots: + void updateNodePermissions(); + private slots: void handlePeerPingTimeout(); private: @@ -64,17 +72,14 @@ private: bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr); - bool isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature, - const HifiSockAddr& senderSockAddr); - bool isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature, - bool& verifiedUsername, - const HifiSockAddr& senderSockAddr); + bool isWithinMaxCapacity(); bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr); void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr); - void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr); + static void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, + DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown); void pingPunchForConnectingPeer(const SharedNetworkPeer& peer); diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp new file mode 100644 index 0000000000..d2762e788b --- /dev/null +++ b/domain-server/src/DomainMetadata.cpp @@ -0,0 +1,280 @@ +// +// DomainMetadata.cpp +// domain-server/src +// +// Created by Zach Pomerantz on 5/25/2016. +// Copyright 2016 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 "DomainMetadata.h" + +#include +#include +#include +#include + +#include "DomainServer.h" +#include "DomainServerNodeData.h" + +const QString DomainMetadata::USERS = "users"; +const QString DomainMetadata::Users::NUM_TOTAL = "num_users"; +const QString DomainMetadata::Users::NUM_ANON = "num_anon_users"; +const QString DomainMetadata::Users::HOSTNAMES = "user_hostnames"; +// users metadata will appear as (JSON): +// { "num_users": Number, +// "num_anon_users": Number, +// "user_hostnames": { : Number } +// } + +const QString DomainMetadata::DESCRIPTORS = "descriptors"; +const QString DomainMetadata::Descriptors::DESCRIPTION = "description"; +const QString DomainMetadata::Descriptors::CAPACITY = "capacity"; // parsed from security +const QString DomainMetadata::Descriptors::RESTRICTION = "restriction"; // parsed from ACL +const QString DomainMetadata::Descriptors::MATURITY = "maturity"; +const QString DomainMetadata::Descriptors::HOSTS = "hosts"; +const QString DomainMetadata::Descriptors::TAGS = "tags"; +const QString DomainMetadata::Descriptors::HOURS = "hours"; +const QString DomainMetadata::Descriptors::Hours::WEEKDAY = "weekday"; +const QString DomainMetadata::Descriptors::Hours::WEEKEND = "weekend"; +const QString DomainMetadata::Descriptors::Hours::UTC_OFFSET = "utc_offset"; +const QString DomainMetadata::Descriptors::Hours::OPEN = "open"; +const QString DomainMetadata::Descriptors::Hours::CLOSE = "close"; +// descriptors metadata will appear as (JSON): +// { "description": String, // capped description +// "capacity": Number, +// "restriction": String, // enum of either open, hifi, or acl +// "maturity": String, // enum corresponding to ESRB ratings +// "hosts": [ String ], // capped list of usernames +// "tags": [ String ], // capped list of tags +// "hours": { +// "utc_offset": Number, +// "weekday": [ [ Time, Time ] ], +// "weekend": [ [ Time, Time ] ], +// } +// } + +// metadata will appear as (JSON): +// { users: , descriptors: } +// +// it is meant to be sent to and consumed by an external API + +// merge delta into target +// target should be of the form [ OpenTime, CloseTime ], +// delta should be of the form [ { open: Time, close: Time } ] +void parseHours(QVariant delta, QVariant& target) { + using Hours = DomainMetadata::Descriptors::Hours; + static const QVariantList DEFAULT_HOURS{ + { QVariantList{ "00:00", "23:59" } } + }; + target.setValue(DEFAULT_HOURS); + + if (!delta.canConvert()) { + return; + } + + auto& deltaList = *static_cast(delta.data()); + if (deltaList.isEmpty()) { + return; + } + + auto& deltaHours = *static_cast(deltaList.first().data()); + auto open = deltaHours.find(Hours::OPEN); + auto close = deltaHours.find(Hours::CLOSE); + if (open == deltaHours.end() || close == deltaHours.end()) { + return; + } + + // merge delta into new hours + static const int OPEN_INDEX = 0; + static const int CLOSE_INDEX = 1; + auto& hours = *static_cast(static_cast(target.data())->first().data()); + hours[OPEN_INDEX] = open.value(); + hours[CLOSE_INDEX] = close.value(); + + assert(hours[OPEN_INDEX].canConvert()); + assert(hours[CLOSE_INDEX].canConvert()); +} + +DomainMetadata::DomainMetadata(QObject* domainServer) : QObject(domainServer) { + // set up the structure necessary for casting during parsing (see parseHours, esp.) + _metadata[USERS] = QVariantMap {}; + _metadata[DESCRIPTORS] = QVariantMap { { + Descriptors::HOURS, QVariantMap { + { Descriptors::Hours::WEEKDAY, QVariant{} }, + { Descriptors::Hours::WEEKEND, QVariant{} } + } + } }; + + assert(dynamic_cast(domainServer)); + DomainServer* server = static_cast(domainServer); + + // update the metadata when a user (dis)connects + connect(server, &DomainServer::userConnected, this, &DomainMetadata::usersChanged); + connect(server, &DomainServer::userDisconnected, this, &DomainMetadata::usersChanged); + + // update the metadata when security changes + connect(&server->_settingsManager, &DomainServerSettingsManager::updateNodePermissions, + this, static_cast(&DomainMetadata::securityChanged)); + + // initialize the descriptors + securityChanged(false); + descriptorsChanged(); +} + +QJsonObject DomainMetadata::get() { + maybeUpdateUsers(); + return QJsonObject::fromVariantMap(_metadata); +} + +QJsonObject DomainMetadata::get(const QString& group) { + maybeUpdateUsers(); + return QJsonObject::fromVariantMap(_metadata[group].toMap()); +} + +void DomainMetadata::descriptorsChanged() { + // get descriptors + assert(_metadata[DESCRIPTORS].canConvert()); + auto& state = *static_cast(_metadata[DESCRIPTORS].data()); + auto& settings = static_cast(parent())->_settingsManager.getSettingsMap(); + auto& descriptors = static_cast(parent())->_settingsManager.getDescriptorsMap(); + + // copy simple descriptors (description/maturity) + state[Descriptors::DESCRIPTION] = descriptors[Descriptors::DESCRIPTION]; + state[Descriptors::MATURITY] = descriptors[Descriptors::MATURITY]; + + // copy array descriptors (hosts/tags) + state[Descriptors::HOSTS] = descriptors[Descriptors::HOSTS].toList(); + state[Descriptors::TAGS] = descriptors[Descriptors::TAGS].toList(); + + // parse capacity + static const QString CAPACITY = "security.maximum_user_capacity"; + const QVariant* capacityVariant = valueForKeyPath(settings, CAPACITY); + unsigned int capacity = capacityVariant ? capacityVariant->toUInt() : 0; + state[Descriptors::CAPACITY] = capacity; + + // parse operating hours + static const QString WEEKDAY_HOURS = "weekday_hours"; + static const QString WEEKEND_HOURS = "weekend_hours"; + static const QString UTC_OFFSET = "utc_offset"; + assert(state[Descriptors::HOURS].canConvert()); + auto& hours = *static_cast(state[Descriptors::HOURS].data()); + hours[Descriptors::Hours::UTC_OFFSET] = descriptors.take(UTC_OFFSET); + parseHours(descriptors[WEEKDAY_HOURS], hours[Descriptors::Hours::WEEKDAY]); + parseHours(descriptors[WEEKEND_HOURS], hours[Descriptors::Hours::WEEKEND]); + +#if DEV_BUILD || PR_BUILD + qDebug() << "Domain metadata descriptors set:" << QJsonObject::fromVariantMap(_metadata[DESCRIPTORS].toMap()); +#endif + + sendDescriptors(); +} + +void DomainMetadata::securityChanged(bool send) { + // get descriptors + assert(_metadata[DESCRIPTORS].canConvert()); + auto& state = *static_cast(_metadata[DESCRIPTORS].data()); + + const QString RESTRICTION_OPEN = "open"; + const QString RESTRICTION_ANON = "anon"; + const QString RESTRICTION_HIFI = "hifi"; + const QString RESTRICTION_ACL = "acl"; + + QString restriction; + + const auto& settingsManager = static_cast(parent())->_settingsManager; + bool hasAnonymousAccess = + settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).canConnectToDomain; + bool hasHifiAccess = + settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).canConnectToDomain; + if (hasAnonymousAccess) { + restriction = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON; + } else if (hasHifiAccess) { + restriction = RESTRICTION_HIFI; + } else { + restriction = RESTRICTION_ACL; + } + + state[Descriptors::RESTRICTION] = restriction; + +#if DEV_BUILD || PR_BUILD + qDebug() << "Domain metadata restriction set:" << restriction; +#endif + + if (send) { + sendDescriptors(); + } +} + +void DomainMetadata::usersChanged() { + ++_tic; + +#if DEV_BUILD || PR_BUILD + qDebug() << "Domain metadata users change detected"; +#endif +} + +void DomainMetadata::maybeUpdateUsers() { + if (_lastTic == _tic) { + return; + } + _lastTic = _tic; + + static const QString DEFAULT_HOSTNAME = "*"; + + auto nodeList = DependencyManager::get(); + int numConnected = 0; + int numConnectedAnonymously = 0; + QVariantMap userHostnames; + + // figure out the breakdown of currently connected interface clients + nodeList->eachNode([&numConnected, &numConnectedAnonymously, &userHostnames](const SharedNodePointer& node) { + auto linkedData = node->getLinkedData(); + if (linkedData) { + auto nodeData = static_cast(linkedData); + + if (!nodeData->wasAssigned()) { + ++numConnected; + + if (nodeData->getUsername().isEmpty()) { + ++numConnectedAnonymously; + } + + // increment the count for this hostname (or the default if we don't have one) + auto placeName = nodeData->getPlaceName(); + auto hostname = placeName.isEmpty() ? DEFAULT_HOSTNAME : placeName; + userHostnames[hostname] = userHostnames[hostname].toInt() + 1; + } + } + }); + + assert(_metadata[USERS].canConvert()); + auto& users = *static_cast(_metadata[USERS].data()); + users[Users::NUM_TOTAL] = numConnected; + users[Users::NUM_ANON] = numConnectedAnonymously; + users[Users::HOSTNAMES] = userHostnames; + +#if DEV_BUILD || PR_BUILD + qDebug() << "Domain metadata users set:" << QJsonObject::fromVariantMap(_metadata[USERS].toMap()); +#endif +} + +void DomainMetadata::sendDescriptors() { + QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(get(DESCRIPTORS)).toJson(QJsonDocument::Compact))); + const QUuid& domainID = DependencyManager::get()->getSessionUUID(); + if (!domainID.isNull()) { + static const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; + QString path { DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)) }; + DependencyManager::get()->sendRequest(path, + AccountManagerAuth::Required, + QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), + domainUpdateJSON.toUtf8()); + +#if DEV_BUILD || PR_BUILD + qDebug() << "Domain metadata sent to" << path; + qDebug() << "Domain metadata update:" << domainUpdateJSON; +#endif + } +} diff --git a/domain-server/src/DomainMetadata.h b/domain-server/src/DomainMetadata.h new file mode 100644 index 0000000000..41f3a60832 --- /dev/null +++ b/domain-server/src/DomainMetadata.h @@ -0,0 +1,75 @@ +// +// DomainMetadata.h +// domain-server/src +// +// Created by Zach Pomerantz on 5/25/2016. +// Copyright 2016 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_DomainMetadata_h +#define hifi_DomainMetadata_h + +#include + +#include +#include + +class DomainMetadata : public QObject { +Q_OBJECT + +public: + using Tic = uint32_t; + + static const QString USERS; + class Users { + public: + static const QString NUM_TOTAL; + static const QString NUM_ANON; + static const QString HOSTNAMES; + }; + + static const QString DESCRIPTORS; + class Descriptors { + public: + static const QString DESCRIPTION; + static const QString CAPACITY; + static const QString RESTRICTION; + static const QString MATURITY; + static const QString HOSTS; + static const QString TAGS; + static const QString HOURS; + class Hours { + public: + static const QString WEEKDAY; + static const QString WEEKEND; + static const QString UTC_OFFSET; + static const QString OPEN; + static const QString CLOSE; + }; + }; + + DomainMetadata(QObject* domainServer); + DomainMetadata() = delete; + + // Get cached metadata + QJsonObject get(); + QJsonObject get(const QString& group); + +public slots: + void descriptorsChanged(); + void securityChanged(bool send); + void securityChanged() { securityChanged(true); } + void usersChanged(); + +protected: + void maybeUpdateUsers(); + void sendDescriptors(); + + QVariantMap _metadata; + uint32_t _lastTic{ (uint32_t)-1 }; + uint32_t _tic{ 0 }; +}; + +#endif // hifi_DomainMetadata_h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index a5c485ebe2..88f4b94883 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -75,9 +76,11 @@ DomainServer::DomainServer(int argc, char* argv[]) : setApplicationVersion(BuildInfo::VERSION); QSettings::setDefaultFormat(QSettings::IniFormat); + qDebug() << "Setting up domain-server"; + // make sure we have a fresh AccountManager instance // (need this since domain-server can restart itself and maintain static variables) - AccountManager::getInstance(true); + DependencyManager::set(); auto args = arguments(); @@ -96,21 +99,38 @@ DomainServer::DomainServer(int argc, char* argv[]) : // make sure we hear about newly connected nodes from our gatekeeper connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode); - if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) { - // we either read a certificate and private key or were not passed one - // and completed login or did not need to + // if a connected node loses connection privileges, hang up on it + connect(&_gatekeeper, &DomainGatekeeper::killNode, this, &DomainServer::handleKillNode); - qDebug() << "Setting up LimitedNodeList and assignments."; - setupNodeListAndAssignments(); + // if permissions are updated, relay the changes to the Node datastructures + connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, + &_gatekeeper, &DomainGatekeeper::updateNodePermissions); - // setup automatic networking settings with data server - setupAutomaticNetworking(); - - // preload some user public keys so they can connect on first request - _gatekeeper.preloadAllowedUserPublicKeys(); - - optionallyGetTemporaryName(args); + // if we were given a certificate/private key or oauth credentials they must succeed + if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) { + return; } + + setupNodeListAndAssignments(); + setupAutomaticNetworking(); + if (!getID().isNull()) { + setupHeartbeatToMetaverse(); + // send the first heartbeat immediately + sendHeartbeatToMetaverse(); + } + + // check for the temporary name parameter + const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; + if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { + getTemporaryName(); + } + + _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request + + _metadata = new DomainMetadata(this); + + + qDebug() << "domain-server is running"; } DomainServer::~DomainServer() { @@ -138,6 +158,10 @@ void DomainServer::restart() { exit(DomainServer::EXIT_CODE_REBOOT); } +const QUuid& DomainServer::getID() { + return DependencyManager::get()->getSessionUUID(); +} + bool DomainServer::optionallyReadX509KeyAndCertificate() { const QString X509_CERTIFICATE_OPTION = "cert"; const QString X509_PRIVATE_KEY_OPTION = "key"; @@ -195,8 +219,8 @@ bool DomainServer::optionallySetupOAuth() { _oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL; } - AccountManager& accountManager = AccountManager::getInstance(); - accountManager.setAuthURL(_oauthProviderURL); + auto accountManager = DependencyManager::get(); + accountManager->setAuthURL(_oauthProviderURL); _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString(); _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV); @@ -223,34 +247,26 @@ bool DomainServer::optionallySetupOAuth() { static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id"; -void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) { - // check for the temporary name parameter - const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; +void DomainServer::getTemporaryName(bool force) { + // check if we already have a domain ID + const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); - if (arguments.contains(GET_TEMPORARY_NAME_SWITCH)) { - - // make sure we don't already have a domain ID - const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); - if (idValueVariant) { - qWarning() << "Temporary domain name requested but a domain ID is already present in domain-server settings." - << "Will not request temporary name."; + qInfo() << "Requesting temporary domain name"; + if (idValueVariant) { + qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString(); + if (force) { + qDebug() << "Requesting temporary domain name to replace current ID:" << getID(); + } else { + qInfo() << "Abandoning request of temporary domain name."; return; } - - // we've been asked to grab a temporary name from the API - // so fire off that request now - auto& accountManager = AccountManager::getInstance(); - - // get callbacks for temporary domain result - JSONCallbackParameters callbackParameters; - callbackParameters.jsonCallbackReceiver = this; - callbackParameters.jsonCallbackMethod = "handleTempDomainSuccess"; - callbackParameters.errorCallbackReceiver = this; - callbackParameters.errorCallbackMethod = "handleTempDomainError"; - - accountManager.sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, - QNetworkAccessManager::PostOperation, callbackParameters); } + + // request a temporary name from the metaverse + auto accountManager = DependencyManager::get(); + JSONCallbackParameters callbackParameters { this, "handleTempDomainSuccess", this, "handleTempDomainError" }; + accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, + QNetworkAccessManager::PostOperation, callbackParameters); } void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { @@ -261,11 +277,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { static const QString DOMAIN_KEY = "domain"; static const QString ID_KEY = "id"; static const QString NAME_KEY = "name"; + static const QString KEY_KEY = "api_key"; auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject(); if (!domainObject.isEmpty()) { auto id = domainObject[ID_KEY].toString(); auto name = domainObject[NAME_KEY].toString(); + auto key = domainObject[KEY_KEY].toString(); qInfo() << "Received new temporary domain name" << name; qDebug() << "The temporary domain ID is" << id; @@ -281,9 +299,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { // change our domain ID immediately DependencyManager::get()->setSessionUUID(QUuid { id }); - // change our automatic networking settings so that we're communicating with the ICE server - setupICEHeartbeatForFullNetworking(); + // store the new token to the account info + auto accountManager = DependencyManager::get(); + accountManager->setTemporaryDomain(id, key); + // update our heartbeats to use the correct id + setupICEHeartbeatForFullNetworking(); + setupHeartbeatToMetaverse(); } else { qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again" << "via domain-server relaunch or from the domain-server settings."; @@ -302,8 +324,27 @@ const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full"; const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip"; const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled"; -void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { + +bool DomainServer::packetVersionMatch(const udt::Packet& packet) { + PacketType headerType = NLPacket::typeInHeader(packet); + PacketVersion headerVersion = NLPacket::versionInHeader(packet); + + auto nodeList = DependencyManager::get(); + + // if this is a mismatching connect packet, we can't simply drop it on the floor + // send back a packet to the interface that tells them we refuse connection for a mismatch + if (headerType == PacketType::DomainConnectRequest + && headerVersion != versionForPacketType(PacketType::DomainConnectRequest)) { + DomainGatekeeper::sendProtocolMismatchConnectionDenial(packet.getSenderSockAddr()); + } + + // let the normal nodeList implementation handle all other packets. + return nodeList->isPacketVerified(packet); +} + + +void DomainServer::setupNodeListAndAssignments() { const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); @@ -348,6 +389,8 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); if (idValueVariant) { nodeList->setSessionUUID(idValueVariant->toString()); + } else { + nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID } connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); @@ -375,6 +418,9 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); + + // set a custum packetVersionMatch as the verify packet operator for the udt::Socket + nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch); } const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; @@ -397,7 +443,7 @@ bool DomainServer::resetAccountManagerAccessToken() { << "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN"; // clear any existing access token from AccountManager - AccountManager::getInstance().setAccessTokenForCurrentAuthURL(QString()); + DependencyManager::get()->setAccessTokenForCurrentAuthURL(QString()); return false; } @@ -407,7 +453,7 @@ bool DomainServer::resetAccountManagerAccessToken() { } // give this access token to the AccountManager - AccountManager::getInstance().setAccessTokenForCurrentAuthURL(accessToken); + DependencyManager::get()->setAccessTokenForCurrentAuthURL(accessToken); return true; @@ -425,29 +471,23 @@ bool DomainServer::resetAccountManagerAccessToken() { } void DomainServer::setupAutomaticNetworking() { - auto nodeList = DependencyManager::get(); + qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; + + resetAccountManagerAccessToken(); _automaticNetworkingSetting = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); + auto nodeList = DependencyManager::get(); + const QUuid& domainID = getID(); + if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { setupICEHeartbeatForFullNetworking(); } - _hasAccessToken = resetAccountManagerAccessToken(); - - if (!_hasAccessToken) { - qDebug() << "Will not send heartbeat to Metaverse API without an access token."; - qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface."; - - return; - } - if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE || _automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { - const QUuid& domainID = nodeList->getSessionUUID(); - if (!domainID.isNull()) { qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID" << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); @@ -459,9 +499,6 @@ void DomainServer::setupAutomaticNetworking() { // have the LNL enable public socket updating via STUN nodeList->startSTUNPublicSocketUpdate(); - } else { - // send our heartbeat to data server so it knows what our network settings are - sendHeartbeatToDataServer(); } } else { qDebug() << "Cannot enable domain-server automatic networking without a domain ID." @@ -469,18 +506,20 @@ void DomainServer::setupAutomaticNetworking() { return; } - } else { - sendHeartbeatToDataServer(); } +} - qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; - - // no matter the auto networking settings we should heartbeat to the data-server every 15s +void DomainServer::setupHeartbeatToMetaverse() { + // heartbeat to the data-server every 15s const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000; - QTimer* dataHeartbeatTimer = new QTimer(this); - connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToDataServer())); - dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS); + if (!_metaverseHeartbeatTimer) { + // setup a timer to heartbeat with the metaverse-server + _metaverseHeartbeatTimer = new QTimer { this }; + connect(_metaverseHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse())); + // do not send a heartbeat immediately - this avoids flooding if the heartbeat fails with a 401 + _metaverseHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS); + } } void DomainServer::setupICEHeartbeatForFullNetworking() { @@ -499,22 +538,21 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { limitedNodeList->startSTUNPublicSocketUpdate(); // to send ICE heartbeats we'd better have a private key locally with an uploaded public key - auto& accountManager = AccountManager::getInstance(); - auto domainID = accountManager.getAccountInfo().getDomainID(); - // if we have an access token and we don't have a private key or the current domain ID has changed // we should generate a new keypair - if (!accountManager.getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) { - accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + auto accountManager = DependencyManager::get(); + if (!accountManager->getAccountInfo().hasPrivateKey() || accountManager->getAccountInfo().getDomainID() != getID()) { + accountManager->generateNewDomainKeypair(getID()); } // hookup to the signal from account manager that tells us when keypair is available - connect(&accountManager, &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange); + connect(accountManager.data(), &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange); if (!_iceHeartbeatTimer) { - // setup a timer to heartbeat with the ice-server every so often + // setup a timer to heartbeat with the ice-server _iceHeartbeatTimer = new QTimer { this }; connect(_iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer); + sendHeartbeatToIceServer(); _iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); } } @@ -665,7 +703,7 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet message, SharedNodePointer sendingNode) { - + QDataStream packetStream(message->getMessage()); NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr(), false); @@ -677,15 +715,22 @@ void DomainServer::processListRequestPacket(QSharedPointer mess DomainServerNodeData* nodeData = reinterpret_cast(sendingNode->getLinkedData()); nodeData->setNodeInterestSet(nodeRequestData.interestList.toSet()); + // update the connecting hostname in case it has changed + nodeData->setPlaceName(nodeRequestData.placeName); + sendDomainListToNode(sendingNode, message->getSenderSockAddr()); } unsigned int DomainServer::countConnectedUsers() { unsigned int result = 0; auto nodeList = DependencyManager::get(); - nodeList->eachNode([&](const SharedNodePointer& otherNode){ - if (otherNode->getType() == NodeType::Agent) { - result++; + nodeList->eachNode([&](const SharedNodePointer& node){ + // only count unassigned agents (i.e., users) + if (node->getType() == NodeType::Agent) { + auto nodeData = static_cast(node->getLinkedData()); + if (nodeData && !nodeData->wasAssigned()) { + result++; + } } }); return result; @@ -731,12 +776,16 @@ QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) { } void DomainServer::handleConnectedNode(SharedNodePointer newNode) { - - DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); - + DomainServerNodeData* nodeData = static_cast(newNode->getLinkedData()); + // reply back to the user with a PacketType::DomainList sendDomainListToNode(newNode, nodeData->getSendingSockAddr()); - + + // if this node is a user (unassigned Agent), signal + if (newNode->getType() == NodeType::Agent && !nodeData->wasAssigned()) { + emit userConnected(); + } + // send out this node to our other connected nodes broadcastNewNode(newNode); } @@ -753,8 +802,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif extendedHeaderStream << limitedNodeList->getSessionUUID(); extendedHeaderStream << node->getUUID(); - extendedHeaderStream << (quint8) node->isAllowedEditor(); - extendedHeaderStream << (quint8) node->getCanRez(); + extendedHeaderStream << node->getPermissions(); auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader); @@ -962,9 +1010,9 @@ void DomainServer::setupPendingAssignmentCredits() { void DomainServer::sendPendingTransactionsToServer() { - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); - if (accountManager.hasValidAccessToken()) { + if (accountManager->hasValidAccessToken()) { // enumerate the pending transactions and send them to the server to complete payment TransactionHash::iterator i = _pendingAssignmentCredits.begin(); @@ -975,7 +1023,7 @@ void DomainServer::sendPendingTransactionsToServer() { transactionCallbackParams.jsonCallbackMethod = "transactionJSONCallback"; while (i != _pendingAssignmentCredits.end()) { - accountManager.sendRequest("api/v1/transactions", + accountManager->sendRequest("api/v1/transactions", AccountManagerAuth::Required, QNetworkAccessManager::PostOperation, transactionCallbackParams, i.value()->postJson().toJson()); @@ -1028,63 +1076,110 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) { const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking"; void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) { - sendHeartbeatToDataServer(newPublicSockAddr.getAddress().toString()); + sendHeartbeatToMetaverse(newPublicSockAddr.getAddress().toString()); } - -void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { - const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; - - auto nodeList = DependencyManager::get(); - const QUuid& domainID = nodeList->getSessionUUID(); - - // setup the domain object to send to the data server - const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address"; - const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking"; - +void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { + // Setup the domain object to send to the data server QJsonObject domainObject; + + // add the versions + static const QString VERSION_KEY = "version"; + domainObject[VERSION_KEY] = BuildInfo::VERSION; + static const QString PROTOCOL_KEY = "protocol"; + domainObject[PROTOCOL_KEY] = protocolVersionsSignatureBase64(); + + // add networking if (!networkAddress.isEmpty()) { + static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address"; domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress; } + static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking"; domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting; - // add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings - const QString RESTRICTED_ACCESS_FLAG = "restricted"; - domainObject[RESTRICTED_ACCESS_FLAG] = - _settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); + // add access level for anonymous connections + // consider the domain to be "restricted" if anonymous connections are disallowed + static const QString RESTRICTED_ACCESS_FLAG = "restricted"; + NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); + domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain; - // add the number of currently connected agent users - int numConnectedAuthedUsers = 0; + const auto& temporaryDomainKey = DependencyManager::get()->getTemporaryDomainKey(getID()); + if (!temporaryDomainKey.isEmpty()) { + // add the temporary domain token + const QString KEY_KEY = "api_key"; + domainObject[KEY_KEY] = temporaryDomainKey; + } - nodeList->eachNode([&numConnectedAuthedUsers](const SharedNodePointer& node){ - if (node->getLinkedData() && !static_cast(node->getLinkedData())->getUsername().isEmpty()) { - ++numConnectedAuthedUsers; - } - }); + if (_metadata) { + // Add the metadata to the heartbeat + static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat"; + domainObject[DOMAIN_HEARTBEAT_KEY] = _metadata->get(DomainMetadata::USERS); + } - const QString DOMAIN_HEARTBEAT_KEY = "heartbeat"; - const QString HEARTBEAT_NUM_USERS_KEY = "num_users"; + QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact))); - QJsonObject heartbeatObject; - heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedAuthedUsers; - domainObject[DOMAIN_HEARTBEAT_KEY] = heartbeatObject; - - QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); - - AccountManager::getInstance().sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), - AccountManagerAuth::Required, + static const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; + QString path = DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())); +#if DEV_BUILD || PR_BUILD + qDebug() << "Domain metadata sent to" << path; + qDebug() << "Domain metadata update:" << domainUpdateJSON; +#endif + DependencyManager::get()->sendRequest(path, + AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), + JSONCallbackParameters(nullptr, QString(), this, "handleMetaverseHeartbeatError"), domainUpdateJSON.toUtf8()); } +void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) { + if (!_metaverseHeartbeatTimer) { + // avoid rehandling errors from the same issue + return; + } + + // check if we need to force a new temporary domain name + switch (requestReply.error()) { + // if we have a temporary domain with a bad token, we get a 401 + case QNetworkReply::NetworkError::AuthenticationRequiredError: { + static const QString DATA_KEY = "data"; + static const QString TOKEN_KEY = "api_key"; + + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY]; + + if (!tokenFailure.isNull()) { + qWarning() << "Temporary domain name lacks a valid API key, and is being reset."; + } + break; + } + // if the domain does not (or no longer) exists, we get a 404 + case QNetworkReply::NetworkError::ContentNotFoundError: + qWarning() << "Domain not found, getting a new temporary domain."; + break; + // otherwise, we erred on something else, and should not force a temporary domain + default: + return; + } + + // halt heartbeats until we have a token + _metaverseHeartbeatTimer->deleteLater(); + _metaverseHeartbeatTimer = nullptr; + + // give up eventually to avoid flooding traffic + static const int MAX_ATTEMPTS = 5; + static int attempt = 0; + if (++attempt < MAX_ATTEMPTS) { + // get a new temporary name and token + getTemporaryName(true); + } else { + qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart."; + } +} + void DomainServer::sendICEServerAddressToMetaverseAPI() { if (!_iceServerSocket.isNull()) { - auto nodeList = DependencyManager::get(); - const QUuid& domainID = nodeList->getSessionUUID(); - const QString ICE_SERVER_ADDRESS = "ice_server_address"; QJsonObject domainObject; @@ -1092,6 +1187,13 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() { // we're using full automatic networking and we have a current ice-server socket, use that now domainObject[ICE_SERVER_ADDRESS] = _iceServerSocket.getAddress().toString(); + const auto& temporaryDomainKey = DependencyManager::get()->getTemporaryDomainKey(getID()); + if (!temporaryDomainKey.isEmpty()) { + // add the temporary domain token + const QString KEY_KEY = "api_key"; + domainObject[KEY_KEY] = temporaryDomainKey; + } + QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); // make sure we hear about failure so we can retry @@ -1103,7 +1205,7 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() { static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address"; - AccountManager::getInstance().sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), + DependencyManager::get()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())), AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, callbackParameters, @@ -1123,15 +1225,15 @@ void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply& requestRepl void DomainServer::sendHeartbeatToIceServer() { if (!_iceServerSocket.getAddress().isNull()) { - auto& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); auto limitedNodeList = DependencyManager::get(); - if (!accountManager.getAccountInfo().hasPrivateKey()) { + if (!accountManager->getAccountInfo().hasPrivateKey()) { qWarning() << "Cannot send an ice-server heartbeat without a private key for signature."; qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat."; if (!limitedNodeList->getSessionUUID().isNull()) { - accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID()); } else { qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported"; } @@ -1208,7 +1310,7 @@ void DomainServer::sendHeartbeatToIceServer() { auto plaintext = QByteArray::fromRawData(_iceServerHeartbeatPacket->getPayload(), _iceServerHeartbeatPacket->getPayloadSize()); // generate a signature for the plaintext data in the packet - auto signature = accountManager.getAccountInfo().signPlaintext(plaintext); + auto signature = accountManager->getAccountInfo().signPlaintext(plaintext); // pack the signature with the data heartbeatDataStream << signature; @@ -1868,11 +1970,10 @@ void DomainServer::nodeAdded(SharedNodePointer node) { } void DomainServer::nodeKilled(SharedNodePointer node) { - // if this peer connected via ICE then remove them from our ICE peers hash _gatekeeper.removeICEPeer(node->getUUID()); - DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(node->getLinkedData()); if (nodeData) { // if this node's UUID matches a static assignment we need to throw it back in the assignment queue @@ -1884,15 +1985,22 @@ void DomainServer::nodeKilled(SharedNodePointer node) { } } - // If this node was an Agent ask DomainServerNodeData to potentially remove the interpolation we stored - nodeData->removeOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY, - uuidStringWithoutCurlyBraces(node->getUUID())); - // cleanup the connection secrets that we set up for this node (on the other nodes) foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) { SharedNodePointer otherNode = DependencyManager::get()->nodeWithUUID(otherNodeSessionUUID); if (otherNode) { - reinterpret_cast(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID()); + static_cast(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID()); + } + } + + if (node->getType() == NodeType::Agent) { + // if this node was an Agent ask DomainServerNodeData to remove the interpolation we potentially stored + nodeData->removeOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY, + uuidStringWithoutCurlyBraces(node->getUUID())); + + // if this node is a user (unassigned Agent), signal + if (!nodeData->wasAssigned()) { + emit userDisconnected(); } } } @@ -2063,35 +2171,42 @@ void DomainServer::processPathQueryPacket(QSharedPointer messag 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 = message->getSourceID(); - + qDebug() << "Received a disconnect request from node with UUID" << nodeUUID; - + // we want to check what type this node was before going to kill it so that we can avoid sending the RemovedNode // packet to nodes that don't care about this type auto nodeToKill = limitedNodeList->nodeWithUUID(nodeUUID); - + if (nodeToKill) { - auto nodeType = nodeToKill->getType(); - limitedNodeList->killNodeWithUUID(nodeUUID); - - static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID); - - removedNodePacket->reset(); - removedNodePacket->write(nodeUUID.toRfc4122()); - - // broadcast out the DomainServerRemovedNode message - limitedNodeList->eachMatchingNode([&nodeType](const SharedNodePointer& otherNode) -> bool { - // only send the removed node packet to nodes that care about the type of node this was - auto nodeLinkedData = dynamic_cast(otherNode->getLinkedData()); - return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType); - }, [&limitedNodeList](const SharedNodePointer& otherNode){ - limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode); - }); + handleKillNode(nodeToKill); } } +void DomainServer::handleKillNode(SharedNodePointer nodeToKill) { + auto nodeType = nodeToKill->getType(); + auto limitedNodeList = DependencyManager::get(); + const QUuid& nodeUUID = nodeToKill->getUUID(); + + limitedNodeList->killNodeWithUUID(nodeUUID); + + static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID); + + removedNodePacket->reset(); + removedNodePacket->write(nodeUUID.toRfc4122()); + + // broadcast out the DomainServerRemovedNode message + limitedNodeList->eachMatchingNode([&nodeType](const SharedNodePointer& otherNode) -> bool { + // only send the removed node packet to nodes that care about the type of node this was + auto nodeLinkedData = dynamic_cast(otherNode->getLinkedData()); + return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType); + }, [&limitedNodeList](const SharedNodePointer& otherNode){ + limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode); + }); +} + void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer message) { static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3; @@ -2101,7 +2216,7 @@ void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer(); - AccountManager::getInstance().generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + DependencyManager::get()->generateNewDomainKeypair(limitedNodeList->getSessionUUID()); // reset our number of heartbeat denials _numHeartbeatDenials = 0; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 93bb5de494..138cb9ca2d 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -26,6 +26,7 @@ #include #include "DomainGatekeeper.h" +#include "DomainMetadata.h" #include "DomainServerSettingsManager.h" #include "DomainServerWebSessionData.h" #include "WalletTransaction.h" @@ -71,7 +72,7 @@ private slots: void sendPendingTransactionsToServer(); void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); - void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); } + void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); } void sendHeartbeatToIceServer(); void handleConnectedNode(SharedNodePointer newNode); @@ -79,6 +80,8 @@ private slots: void handleTempDomainSuccess(QNetworkReply& requestReply); void handleTempDomainError(QNetworkReply& requestReply); + void handleMetaverseHeartbeatError(QNetworkReply& requestReply); + void queuedQuit(QString quitMessage, int exitCode); void handleKeypairChange(); @@ -91,24 +94,33 @@ private slots: signals: void iceServerChanged(); + void userConnected(); + void userDisconnected(); private: - void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); + const QUuid& getID(); + + void setupNodeListAndAssignments(); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); - void optionallyGetTemporaryName(const QStringList& arguments); + void getTemporaryName(bool force = false); + + static bool packetVersionMatch(const udt::Packet& packet); bool resetAccountManagerAccessToken(); void setupAutomaticNetworking(); void setupICEHeartbeatForFullNetworking(); - void sendHeartbeatToDataServer(const QString& networkAddress); + void setupHeartbeatToMetaverse(); + void sendHeartbeatToMetaverse(const QString& networkAddress); void randomizeICEServerAddress(bool shouldTriggerHostLookup); unsigned int countConnectedUsers(); + void handleKillNode(SharedNodePointer nodeToKill); + void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr); QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB); @@ -168,7 +180,10 @@ private: HifiSockAddr _iceServerSocket; std::unique_ptr _iceServerHeartbeatPacket; - QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer + // These will be parented to this, they are not dangling + DomainMetadata* _metadata { nullptr }; + QTimer* _iceHeartbeatTimer { nullptr }; + QTimer* _metaverseHeartbeatTimer { nullptr }; QList _iceServerAddresses; QSet _failedIceServerAddresses; @@ -177,9 +192,8 @@ private: int _numHeartbeatDenials { 0 }; bool _connectedToICEServer { false }; - bool _hasAccessToken { false }; - friend class DomainGatekeeper; + friend class DomainMetadata; }; diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index cd68aa3006..f95403c779 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -56,6 +56,12 @@ public: void addOverrideForKey(const QString& key, const QString& value, const QString& overrideValue); void removeOverrideForKey(const QString& key, const QString& value); + + const QString& getPlaceName() { return _placeName; } + void setPlaceName(const QString& placeName) { _placeName = placeName; } + + bool wasAssigned() const { return _wasAssigned; }; + void setWasAssigned(bool wasAssigned) { _wasAssigned = wasAssigned; } private: QJsonObject overrideValuesIfNeeded(const QJsonObject& newStats); @@ -75,6 +81,10 @@ private: bool _isAuthenticated = true; NodeSet _nodeInterestSet; QString _nodeVersion; + + QString _placeName; + + bool _wasAssigned { false }; }; #endif // hifi_DomainServerNodeData_h diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 0ca0cf8232..262cc9d9ee 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include #include @@ -19,13 +21,19 @@ #include #include +#include + #include #include #include #include +#include #include "DomainServerSettingsManager.h" +#define WANT_DEBUG 1 + + const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; const QString DESCRIPTION_SETTINGS_KEY = "settings"; @@ -44,7 +52,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() : QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH); descriptionFile.open(QIODevice::ReadOnly); - QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll()); + QJsonParseError parseError; + QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll(), &parseError); if (descriptionDocument.isObject()) { QJsonObject descriptionObject = descriptionDocument.object(); @@ -63,8 +72,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() : } static const QString MISSING_SETTINGS_DESC_MSG = - QString("Did not find settings decription in JSON at %1 - Unable to continue. domain-server will quit.") - .arg(SETTINGS_DESCRIPTION_RELATIVE_PATH); + QString("Did not find settings description in JSON at %1 - Unable to continue. domain-server will quit.\n%2 at %3") + .arg(SETTINGS_DESCRIPTION_RELATIVE_PATH).arg(parseError.errorString()).arg(parseError.offset); static const int MISSING_SETTINGS_DESC_ERROR_CODE = 6; QMetaObject::invokeMethod(QCoreApplication::instance(), "queuedQuit", Qt::QueuedConnection, @@ -88,7 +97,8 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointersetAll(true); + _standardAgentPermissions[NodePermissions::standardNameAnonymous].reset( + new NodePermissions(NodePermissions::standardNameAnonymous)); + _standardAgentPermissions[NodePermissions::standardNameLoggedIn].reset( + new NodePermissions(NodePermissions::standardNameLoggedIn)); + + if (isRestrictedAccess) { + // only users in allow-users list can connect + _standardAgentPermissions[NodePermissions::standardNameAnonymous]->canConnectToDomain = false; + _standardAgentPermissions[NodePermissions::standardNameLoggedIn]->canConnectToDomain = false; + } // else anonymous and logged-in retain default of canConnectToDomain = true + + foreach (QString allowedUser, allowedUsers) { + // even if isRestrictedAccess is false, we have to add explicit rows for these users. + // defaults to canConnectToDomain = true + _agentPermissions[allowedUser].reset(new NodePermissions(allowedUser)); + } + + foreach (QString allowedEditor, allowedEditors) { + if (!_agentPermissions.contains(allowedEditor)) { + _agentPermissions[allowedEditor].reset(new NodePermissions(allowedEditor)); + if (isRestrictedAccess) { + // they can change locks, but can't connect. + _agentPermissions[allowedEditor]->canConnectToDomain = false; + } + } + _agentPermissions[allowedEditor]->canAdjustLocks = true; + } + + QList> permissionsSets; + permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get(); + foreach (auto permissionsSet, permissionsSets) { + foreach (QString userName, permissionsSet.keys()) { + if (onlyEditorsAreRezzers) { + permissionsSet[userName]->canRezPermanentEntities = permissionsSet[userName]->canAdjustLocks; + permissionsSet[userName]->canRezTemporaryEntities = permissionsSet[userName]->canAdjustLocks; + } else { + permissionsSet[userName]->canRezPermanentEntities = true; + permissionsSet[userName]->canRezTemporaryEntities = true; + } + } + } + + packPermissions(); + _standardAgentPermissions.clear(); + _agentPermissions.clear(); + } + + if (oldVersion < 1.5) { + // This was prior to operating hours, so add default hours + validateDescriptorsMap(); + } } + unpackPermissions(); + // write the current description version to our settings appSettings.setValue(JSON_SETTINGS_VERSION_KEY, _descriptionVersion); } +QVariantMap& DomainServerSettingsManager::getDescriptorsMap() { + validateDescriptorsMap(); + + static const QString DESCRIPTORS{ "descriptors" }; + return *static_cast(getSettingsMap()[DESCRIPTORS].data()); +} + +void DomainServerSettingsManager::validateDescriptorsMap() { + static const QString WEEKDAY_HOURS{ "descriptors.weekday_hours" }; + static const QString WEEKEND_HOURS{ "descriptors.weekend_hours" }; + static const QString UTC_OFFSET{ "descriptors.utc_offset" }; + + QVariant* weekdayHours = valueForKeyPath(_configMap.getUserConfig(), WEEKDAY_HOURS, true); + QVariant* weekendHours = valueForKeyPath(_configMap.getUserConfig(), WEEKEND_HOURS, true); + QVariant* utcOffset = valueForKeyPath(_configMap.getUserConfig(), UTC_OFFSET, true); + + static const QString OPEN{ "open" }; + static const QString CLOSE{ "close" }; + static const QString DEFAULT_OPEN{ "00:00" }; + static const QString DEFAULT_CLOSE{ "23:59" }; + bool wasMalformed = false; + if (weekdayHours->isNull()) { + *weekdayHours = QVariantList{ QVariantMap{ { OPEN, QVariant(DEFAULT_OPEN) }, { CLOSE, QVariant(DEFAULT_CLOSE) } } }; + wasMalformed = true; + } + if (weekendHours->isNull()) { + *weekendHours = QVariantList{ QVariantMap{ { OPEN, QVariant(DEFAULT_OPEN) }, { CLOSE, QVariant(DEFAULT_CLOSE) } } }; + wasMalformed = true; + } + if (utcOffset->isNull()) { + *utcOffset = QVariant(QTimeZone::systemTimeZone().offsetFromUtc(QDateTime::currentDateTime()) / (float)SECS_PER_HOUR); + wasMalformed = true; + } + + if (wasMalformed) { + // write the new settings to file + persistToFile(); + + // reload the master and user config so the merged config is correct + _configMap.loadMasterAndUserConfig(_argumentList); + } +} + +void DomainServerSettingsManager::packPermissionsForMap(QString mapName, + NodePermissionsMap& agentPermissions, + QString keyPath) { + QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security"); + if (!security || !security->canConvert(QMetaType::QVariantMap)) { + security = valueForKeyPath(_configMap.getUserConfig(), "security", true); + (*security) = QVariantMap(); + } + + // save settings for anonymous / logged-in / localhost + QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath); + if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { + permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath, true); + (*permissions) = QVariantList(); + } + + QVariantList* permissionsList = reinterpret_cast(permissions); + (*permissionsList).clear(); + foreach (QString userName, agentPermissions.keys()) { + *permissionsList += agentPermissions[userName]->toVariant(); + } +} + +void DomainServerSettingsManager::packPermissions() { + // transfer details from _agentPermissions to _configMap + packPermissionsForMap("standard_permissions", _standardAgentPermissions, AGENT_STANDARD_PERMISSIONS_KEYPATH); + + // save settings for specific users + packPermissionsForMap("permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH); + + persistToFile(); + _configMap.loadMasterAndUserConfig(_argumentList); +} + +void DomainServerSettingsManager::unpackPermissions() { + // transfer details from _configMap to _agentPermissions; + + _standardAgentPermissions.clear(); + _agentPermissions.clear(); + + bool foundLocalhost = false; + bool foundAnonymous = false; + bool foundLoggedIn = false; + bool needPack = false; + + QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH); + if (!standardPermissions || !standardPermissions->canConvert(QMetaType::QVariantList)) { + qDebug() << "failed to extract standard permissions from settings."; + standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH, true); + (*standardPermissions) = QVariantList(); + } + QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); + if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { + qDebug() << "failed to extract permissions from settings."; + permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH, true); + (*permissions) = QVariantList(); + } + + QList standardPermissionsList = standardPermissions->toList(); + foreach (QVariant permsHash, standardPermissionsList) { + NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; + QString id = perms->getID(); + foundLocalhost |= (id == NodePermissions::standardNameLocalhost); + foundAnonymous |= (id == NodePermissions::standardNameAnonymous); + foundLoggedIn |= (id == NodePermissions::standardNameLoggedIn); + if (_standardAgentPermissions.contains(id)) { + qDebug() << "duplicate name in standard permissions table: " << id; + _standardAgentPermissions[id] |= perms; + needPack = true; + } else { + _standardAgentPermissions[id] = perms; + } + } + + QList permissionsList = permissions->toList(); + foreach (QVariant permsHash, permissionsList) { + NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; + QString id = perms->getID(); + if (_agentPermissions.contains(id)) { + qDebug() << "duplicate name in permissions table: " << id; + _agentPermissions[id] |= perms; + needPack = true; + } else { + _agentPermissions[id] = perms; + } + } + + // if any of the standard names are missing, add them + if (!foundLocalhost) { + NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) }; + perms->setAll(true); + _standardAgentPermissions[perms->getID()] = perms; + needPack = true; + } + if (!foundAnonymous) { + NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) }; + _standardAgentPermissions[perms->getID()] = perms; + needPack = true; + } + if (!foundLoggedIn) { + NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) }; + _standardAgentPermissions[perms->getID()] = perms; + needPack = true; + } + + if (needPack) { + packPermissions(); + } + + #ifdef WANT_DEBUG + qDebug() << "--------------- permissions ---------------------"; + QList> permissionsSets; + permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get(); + foreach (auto permissionSet, permissionsSets) { + QHashIterator i(permissionSet); + while (i.hasNext()) { + i.next(); + NodePermissionsPointer perms = i.value(); + qDebug() << i.key() << perms; + } + } + #endif +} + +NodePermissions DomainServerSettingsManager::getStandardPermissionsForName(const QString& name) const { + if (_standardAgentPermissions.contains(name)) { + return *(_standardAgentPermissions[name].get()); + } + NodePermissions nullPermissions; + nullPermissions.setAll(false); + return nullPermissions; +} + +NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString& name) const { + if (_agentPermissions.contains(name)) { + return *(_agentPermissions[name].get()); + } + NodePermissions nullPermissions; + nullPermissions.setAll(false); + return nullPermissions; +} + QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) { const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath); @@ -257,7 +521,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection qDebug() << "DomainServerSettingsManager postedObject -" << postedObject; // we recurse one level deep below each group for the appropriate setting - recurseJSONObjectAndOverwriteSettings(postedObject); + bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject); // store whatever the current _settingsMap is to file persistToFile(); @@ -267,8 +531,13 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json"); // defer a restart to the domain-server, this gives our HTTPConnection enough time to respond - const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000; - QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart())); + if (restartRequired) { + const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000; + QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart())); + } else { + unpackPermissions(); + emit updateNodePermissions(); + } return true; } else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) { @@ -282,7 +551,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true); rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object(); - connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); } @@ -458,6 +726,8 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV // TODO: we still need to recurse here with the description in case values in the array have special types settingMap[key] = newValue.toArray().toVariantList(); } + + sortPermissions(); } QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) { @@ -471,9 +741,10 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson return QJsonObject(); } -void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { +bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { auto& settingsVariant = _configMap.getUserConfig(); - + bool needRestart = false; + // Iterate on the setting groups foreach(const QString& rootKey, postedObject.keys()) { QJsonValue rootValue = postedObject[rootKey]; @@ -521,6 +792,9 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject); + if (rootKey != "security") { + needRestart = true; + } } else { qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting."; } @@ -534,6 +808,9 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { QJsonValue settingValue = rootValue.toObject()[settingKey]; updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject); + if (rootKey != "security") { + needRestart = true; + } } else { qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey << "- cannot update setting."; @@ -549,9 +826,42 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ // re-merge the user and master configs after a settings change _configMap.mergeMasterAndUserConfigs(); + + return needRestart; +} + +// Compare two members of a permissions list +bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) { + if (!v1.canConvert(QMetaType::QVariantMap) || + !v2.canConvert(QMetaType::QVariantMap)) { + return v1.toString() < v2.toString(); + } + QVariantMap m1 = v1.toMap(); + QVariantMap m2 = v2.toMap(); + + if (!m1.contains("permissions_id") || + !m2.contains("permissions_id")) { + return v1.toString() < v2.toString(); + } + return m1["permissions_id"].toString() < m2["permissions_id"].toString(); +} + +void DomainServerSettingsManager::sortPermissions() { + // sort the permission-names + QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH); + if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) { + QList* standardPermissionsList = reinterpret_cast(standardPermissions); + std::sort((*standardPermissionsList).begin(), (*standardPermissionsList).end(), permissionVariantLessThan); + } + QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); + if (permissions && permissions->canConvert(QMetaType::QVariantList)) { + QList* permissionsList = reinterpret_cast(permissions); + std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); + } } void DomainServerSettingsManager::persistToFile() { + sortPermissions(); // make sure we have the dir the settings file is supposed to live in QFileInfo settingsFileInfo(_configMap.getUserConfigFilename()); diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index d6dd5070a9..66f1a83500 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -19,14 +19,14 @@ #include #include +#include "NodePermissions.h" const QString SETTINGS_PATHS_KEY = "paths"; const QString SETTINGS_PATH = "/settings"; const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; - -const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users"; -const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access"; +const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions"; +const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; class DomainServerSettingsManager : public QObject { Q_OBJECT @@ -41,16 +41,31 @@ public: QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); } QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); } + QVariantMap& getDescriptorsMap(); + + bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name); } + bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); } + NodePermissions getStandardPermissionsForName(const QString& name) const; + NodePermissions getPermissionsForName(const QString& name) const; + QStringList getAllNames() { return _agentPermissions.keys(); } + +signals: + void updateNodePermissions(); + + private slots: void processSettingsRequestPacket(QSharedPointer message); private: + QStringList _argumentList; + QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false); - void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject); + bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject); void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, const QJsonObject& settingDescription); QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName); + void sortPermissions(); void persistToFile(); double _descriptionVersion; @@ -58,6 +73,14 @@ private: HifiConfigVariantMap _configMap; friend class DomainServer; + + void validateDescriptorsMap(); + + void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath); + void packPermissions(); + void unpackPermissions(); + NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost + NodePermissionsMap _agentPermissions; // specific account-names }; #endif // hifi_DomainServerSettingsManager_h diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index 80cb5950be..13bb9123d8 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -19,11 +19,21 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c if (isConnectRequest) { dataStream >> newHeader.connectUUID; + + // Read out the protocol version signature from the connect message + char* rawBytes; + uint length; + + dataStream.readBytes(rawBytes, length); + newHeader.protocolVersion = QByteArray(rawBytes, length); + + // NOTE: QDataStream::readBytes() - The buffer is allocated using new []. Destroy it with the delete [] operator. + delete[] rawBytes; } dataStream >> newHeader.nodeType >> newHeader.publicSockAddr >> newHeader.localSockAddr - >> newHeader.interestList; + >> newHeader.interestList >> newHeader.placeName; newHeader.senderSockAddr = senderSockAddr; diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h index 6b3b8eb7c1..9264db637e 100644 --- a/domain-server/src/NodeConnectionData.h +++ b/domain-server/src/NodeConnectionData.h @@ -27,6 +27,9 @@ public: HifiSockAddr localSockAddr; HifiSockAddr senderSockAddr; QList interestList; + QString placeName; + + QByteArray protocolVersion; }; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 4f58cf73db..414fafe705 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -4,10 +4,6 @@ project(${TARGET_NAME}) # set a default root dir for each of our optional externals if it was not passed set(OPTIONAL_EXTERNALS "LeapMotion") -if(WIN32) - list(APPEND OPTIONAL_EXTERNALS "3DConnexionClient") -endif() - foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE) if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR) @@ -62,6 +58,7 @@ set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}") # qt5_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS}) if (APPLE) + # configure CMake to use a custom Info.plist set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) @@ -143,7 +140,7 @@ link_hifi_libraries(shared octree gpu gl gpu-gl procedural model render recording fbx networking model-networking entities avatars audio audio-client animation script-engine physics render-utils entities-renderer ui auto-updater - controllers plugins display-plugins input-plugins) + controllers plugins ui-plugins display-plugins input-plugins steamworks-wrapper) # include the binary directory of render-utils for shader includes target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils") @@ -233,6 +230,13 @@ if (APPLE) set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources") + # copy script files beside the executable + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_SOURCE_DIR}/scripts" + $/../Resources/scripts + ) + # call the fixup_interface macro to add required bundling commands for installation fixup_interface() @@ -267,6 +271,7 @@ else (APPLE) endif (APPLE) if (SCRIPTS_INSTALL_DIR) + # setup install of scripts beside interface executable install( DIRECTORY "${CMAKE_SOURCE_DIR}/scripts/" diff --git a/interface/resources/controllers/hydra.json b/interface/resources/controllers/hydra.json index 42237033af..716d283a86 100644 --- a/interface/resources/controllers/hydra.json +++ b/interface/resources/controllers/hydra.json @@ -3,9 +3,17 @@ "channels": [ { "from": "Hydra.LY", "filters": "invert", "to": "Standard.LY" }, { "from": "Hydra.LX", "to": "Standard.LX" }, + { "from": "Hydra.LT", "to": "Standard.LTClick", + "peek": true, + "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] + }, { "from": "Hydra.LT", "to": "Standard.LT" }, { "from": "Hydra.RY", "filters": "invert", "to": "Standard.RY" }, { "from": "Hydra.RX", "to": "Standard.RX" }, + { "from": "Hydra.RT", "to": "Standard.RTClick", + "peek": true, + "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] + }, { "from": "Hydra.RT", "to": "Standard.RT" }, { "from": "Hydra.LB", "to": "Standard.LB" }, @@ -16,8 +24,10 @@ { "from": "Hydra.L0", "to": "Standard.Back" }, { "from": "Hydra.R0", "to": "Standard.Start" }, - { "from": [ "Hydra.L1", "Hydra.L2", "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" }, - { "from": [ "Hydra.R1", "Hydra.R2", "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" }, + { "from": [ "Hydra.L1", "Hydra.L3" ], "to": "Standard.LeftPrimaryThumb" }, + { "from": [ "Hydra.R1", "Hydra.R3" ], "to": "Standard.RightPrimaryThumb" }, + { "from": [ "Hydra.R2", "Hydra.R4" ], "to": "Standard.RightSecondaryThumb" }, + { "from": [ "Hydra.L2", "Hydra.L4" ], "to": "Standard.LeftSecondaryThumb" }, { "from": "Hydra.LeftHand", "to": "Standard.LeftHand" }, { "from": "Hydra.RightHand", "to": "Standard.RightHand" } diff --git a/interface/resources/controllers/neuron.json b/interface/resources/controllers/neuron.json index 2d61f80c35..d0962c72db 100644 --- a/interface/resources/controllers/neuron.json +++ b/interface/resources/controllers/neuron.json @@ -1,7 +1,7 @@ { "name": "Neuron to Standard", "channels": [ - { "from": "Hydra.LeftHand", "to": "Standard.LeftHand" }, - { "from": "Hydra.RightHand", "to": "Standard.RightHand" } + { "from": "Neuron.LeftHand", "to": "Standard.LeftHand" }, + { "from": "Neuron.RightHand", "to": "Standard.RightHand" } ] } diff --git a/interface/resources/controllers/oculus_touch.json b/interface/resources/controllers/oculus_touch.json index cc8c2f8bdc..001d5b8716 100644 --- a/interface/resources/controllers/oculus_touch.json +++ b/interface/resources/controllers/oculus_touch.json @@ -1,22 +1,48 @@ { "name": "Oculus Touch to Standard", "channels": [ - { "from": "OculusTouch.LY", "filters": "invert", "to": "Standard.LY" }, - { "from": "OculusTouch.LX", "to": "Standard.LX" }, + { "from": "OculusTouch.A", "to": "Standard.RightPrimaryThumb" }, + { "from": "OculusTouch.B", "to": "Standard.RightSecondaryThumb" }, + { "from": "OculusTouch.X", "to": "Standard.LeftPrimaryThumb" }, + { "from": "OculusTouch.Y", "to": "Standard.LeftSecondaryThumb" }, + + { "from": "OculusTouch.LY", "filters": "invert", "to": "Standard.LY" }, + { "from": "OculusTouch.LX", "to": "Standard.LX" }, + { "from": "OculusTouch.LT", "to": "Standard.LTClick", + "peek": true, + "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] + }, { "from": "OculusTouch.LT", "to": "Standard.LT" }, + { "from": "OculusTouch.LS", "to": "Standard.LS" }, + { "from": "OculusTouch.LeftGrip", "to": "Standard.LeftGrip" }, + { "from": "OculusTouch.LeftHand", "to": "Standard.LeftHand" }, - { "from": "OculusTouch.RY", "filters": "invert", "to": "Standard.RY" }, - { "from": "OculusTouch.RX", "to": "Standard.RX" }, - + { "from": "OculusTouch.RY", "filters": "invert", "to": "Standard.RY" }, + { "from": "OculusTouch.RX", "to": "Standard.RX" }, + { "from": "OculusTouch.RT", "to": "Standard.RTClick", + "peek": true, + "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] + }, { "from": "OculusTouch.RT", "to": "Standard.RT" }, - { "from": "OculusTouch.RB", "to": "Standard.RB" }, { "from": "OculusTouch.RS", "to": "Standard.RS" }, + { "from": "OculusTouch.RightGrip", "to": "Standard.RightGrip" }, + { "from": "OculusTouch.RightHand", "to": "Standard.RightHand" }, { "from": "OculusTouch.LeftApplicationMenu", "to": "Standard.Back" }, { "from": "OculusTouch.RightApplicationMenu", "to": "Standard.Start" }, - { "from": "OculusTouch.LeftHand", "to": "Standard.LeftHand" }, - { "from": "OculusTouch.RightHand", "to": "Standard.RightHand" } + { "from": "OculusTouch.LeftPrimaryThumbTouch", "to": "Standard.LeftPrimaryThumbTouch" }, + { "from": "OculusTouch.LeftSecondaryThumbTouch", "to": "Standard.LeftSecondaryThumbTouch" }, + { "from": "OculusTouch.RightPrimaryThumbTouch", "to": "Standard.RightPrimaryThumbTouch" }, + { "from": "OculusTouch.RightSecondaryThumbTouch", "to": "Standard.RightSecondaryThumbTouch" }, + { "from": "OculusTouch.LeftPrimaryIndexTouch", "to": "Standard.LeftPrimaryIndexTouch" }, + { "from": "OculusTouch.RightPrimaryIndexTouch", "to": "Standard.RightPrimaryIndexTouch" }, + { "from": "OculusTouch.LSTouch", "to": "Standard.LSTouch" }, + { "from": "OculusTouch.RSTouch", "to": "Standard.RSTouch" }, + { "from": "OculusTouch.LeftThumbUp", "to": "Standard.LeftThumbUp" }, + { "from": "OculusTouch.RightThumbUp", "to": "Standard.RightThumbUp" }, + { "from": "OculusTouch.LeftIndexPoint", "to": "Standard.LeftIndexPoint" }, + { "from": "OculusTouch.RightIndexPoint", "to": "Standard.RightIndexPoint" } ] } diff --git a/interface/resources/controllers/spacemouse.json b/interface/resources/controllers/spacemouse.json index adcfef71a6..1480e1957d 100644 --- a/interface/resources/controllers/spacemouse.json +++ b/interface/resources/controllers/spacemouse.json @@ -2,11 +2,11 @@ "name": "Spacemouse to Standard", "channels": [ - { "from": "Spacemouse.TranslateX", "to": "Standard.RX" }, + { "from": "Spacemouse.TranslateX", "to": "Standard.LX" }, { "from": "Spacemouse.TranslateY", "to": "Standard.LY" }, { "from": "Spacemouse.TranslateZ", "to": "Standard.RY" }, - { "from": "Spacemouse.RotateZ", "to": "Standard.LX" }, + { "from": "Spacemouse.RotateZ", "to": "Standard.RX" }, { "from": "Spacemouse.LeftButton", "to": "Standard.LB" }, { "from": "Spacemouse.RightButton", "to": "Standard.RB" } diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index f25e0cc3c4..5c0ea09939 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -9,6 +9,8 @@ "to": "Actions.StepYaw", "filters": [ + { "type": "deadZone", "min": 0.15 }, + "constrainToInteger", { "type": "pulse", "interval": 0.5 }, { "type": "scale", "scale": 22.5 } ] diff --git a/interface/resources/controllers/standard_navigation.json b/interface/resources/controllers/standard_navigation.json index c9dc90cbe6..81096a7230 100644 --- a/interface/resources/controllers/standard_navigation.json +++ b/interface/resources/controllers/standard_navigation.json @@ -10,14 +10,7 @@ { "from": "Standard.RB", "to": "Actions.UiNavGroup" }, { "from": [ "Standard.A", "Standard.X" ], "to": "Actions.UiNavSelect" }, { "from": [ "Standard.B", "Standard.Y" ], "to": "Actions.UiNavBack" }, - { - "from": [ "Standard.RT", "Standard.LT" ], - "to": "Actions.UiNavSelect", - "filters": [ - { "type": "deadZone", "min": 0.5 }, - "constrainToInteger" - ] - }, + { "from": [ "Standard.RTClick", "Standard.LTClick" ], "to": "Actions.UiNavSelect" }, { "from": "Standard.LX", "to": "Actions.UiNavLateral", "filters": [ diff --git a/interface/resources/controllers/touchscreen.json b/interface/resources/controllers/touchscreen.json new file mode 100644 index 0000000000..5b2ff62a8d --- /dev/null +++ b/interface/resources/controllers/touchscreen.json @@ -0,0 +1,23 @@ +{ + "name": "Touchscreen to Actions", + "channels": [ + { "from": "Touchscreen.GesturePinchOut", "to": "Actions.BoomOut", "filters": [ { "type": "scale", "scale": 0.02 } ] }, + { "from": "Touchscreen.GesturePinchIn", "to": "Actions.BoomIn", "filters": [ { "type": "scale", "scale": 0.02 } ] }, + + { "from": { "makeAxis" : [ + [ "Touchscreen.DragLeft" ], + [ "Touchscreen.DragRight" ] + ] + }, + "to": "Actions.Yaw", "filters": [ { "type": "scale", "scale": 0.12 } ] + }, + + { "from": { "makeAxis" : [ + [ "Touchscreen.DragUp" ], + [ "Touchscreen.DragDown" ] + ] + }, + "to": "Actions.Pitch", "filters": [ { "type": "scale", "scale": 0.04 } ] + } + ] +} diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 4085d71c27..79114b8141 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -1,24 +1,38 @@ { "name": "Vive to Standard", "channels": [ - { "from": "Vive.LY", "when": "Vive.LS", "filters": ["invert" ,{ "type": "deadZone", "min": 0.6 }], "to": "Standard.LY" }, - { "from": "Vive.LX", "when": "Vive.LS", "filters": [{ "type": "deadZone", "min": 0.6 }], "to": "Standard.LX" }, + { "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" }, + { "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" }, + { + "from": "Vive.LT", "to": "Standard.LT", + "filters": [ + { "type": "deadZone", "min": 0.05 } + ] + }, + { "from": "Vive.LTClick", "to": "Standard.LTClick" }, - { "from": "Vive.LT", "to": "Standard.LT" }, - { "from": "Vive.LeftGrip", "to": "Standard.LB" }, + { "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" }, { "from": "Vive.LS", "to": "Standard.LS" }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, - { "from": "Vive.RY", "when": "Vive.RS", "filters": ["invert", { "type": "deadZone", "min": 0.6 }], "to": "Standard.RY" }, - { "from": "Vive.RX", "when": "Vive.RS", "filters": [{ "type": "deadZone", "min": 0.6 }], "to": "Standard.RX" }, + { "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" }, + { "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" }, + { + "from": "Vive.RT", "to": "Standard.RT", + "filters": [ + { "type": "deadZone", "min": 0.05 } + ] + }, + { "from": "Vive.RTClick", "to": "Standard.RTClick" }, - { "from": "Vive.RT", "to": "Standard.RT" }, - { "from": "Vive.RightGrip", "to": "Standard.RB" }, + { "from": "Vive.RightGrip", "to": "Standard.RightGrip" }, { "from": "Vive.RS", "to": "Standard.RS" }, { "from": "Vive.RSTouch", "to": "Standard.RSTouch" }, - { "from": "Vive.LeftApplicationMenu", "to": "Standard.Back" }, - { "from": "Vive.RightApplicationMenu", "to": "Standard.Start" }, + { "from": "Vive.LSCenter", "to": "Standard.LeftPrimaryThumb" }, + { "from": "Vive.LeftApplicationMenu", "to": "Standard.LeftSecondaryThumb" }, + { "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" }, + { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, { "from": "Vive.RightHand", "to": "Standard.RightHand" } diff --git a/interface/resources/controllers/xbox.json b/interface/resources/controllers/xbox.json index 8c341dff83..fdac70ff33 100644 --- a/interface/resources/controllers/xbox.json +++ b/interface/resources/controllers/xbox.json @@ -1,14 +1,14 @@ { "name": "XBox to Standard", "channels": [ - { "from": "GamePad.LY", "to": "Standard.LY" }, - { "from": "GamePad.LX", "to": "Standard.LX" }, + { "from": "GamePad.LY", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.LY" }, + { "from": "GamePad.LX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.LX" }, { "from": "GamePad.LT", "to": "Standard.LT" }, { "from": "GamePad.LB", "to": "Standard.LB" }, { "from": "GamePad.LS", "to": "Standard.LS" }, - { "from": "GamePad.RY", "to": "Standard.RY" }, - { "from": "GamePad.RX", "to": "Standard.RX" }, + { "from": "GamePad.RY", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.RY" }, + { "from": "GamePad.RX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.RX" }, { "from": "GamePad.RT", "to": "Standard.RT" }, { "from": "GamePad.RB", "to": "Standard.RB" }, { "from": "GamePad.RS", "to": "Standard.RS" }, diff --git a/interface/resources/fonts/FiraSans-Regular.ttf b/interface/resources/fonts/FiraSans-Regular.ttf new file mode 100644 index 0000000000..d9fdc0e922 Binary files /dev/null and b/interface/resources/fonts/FiraSans-Regular.ttf differ diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 1b0d4f3fe6..89d4767012 100644 Binary files a/interface/resources/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/icons/hud.svg b/interface/resources/icons/hud.svg new file mode 100644 index 0000000000..acfd21b909 --- /dev/null +++ b/interface/resources/icons/hud.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/images/Loading-Inner-H.png b/interface/resources/images/Loading-Inner-H.png new file mode 100644 index 0000000000..cf09ea8317 Binary files /dev/null and b/interface/resources/images/Loading-Inner-H.png differ diff --git a/interface/resources/images/Loading-Outer-Ring.png b/interface/resources/images/Loading-Outer-Ring.png new file mode 100644 index 0000000000..52613949e5 Binary files /dev/null and b/interface/resources/images/Loading-Outer-Ring.png differ diff --git a/interface/resources/images/default-domain.gif b/interface/resources/images/default-domain.gif new file mode 100644 index 0000000000..1418a54cdd Binary files /dev/null and b/interface/resources/images/default-domain.gif differ diff --git a/interface/resources/images/preview.png b/interface/resources/images/preview.png new file mode 100644 index 0000000000..faebbfce8f Binary files /dev/null and b/interface/resources/images/preview.png differ diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index 7f107e44e9..792410c59d 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -13,6 +13,7 @@ import QtQuick 2.4 import "controls" import "styles" import "windows" +import "hifi" Window { id: root @@ -20,15 +21,17 @@ Window { objectName: "AddressBarDialog" frame: HiddenFrame {} + hideBackground: true - visible: false - destroyOnInvisible: false + shown: false + destroyOnHidden: false resizable: false scale: 1.25 // Make this dialog a little larger than normal width: addressBarDialog.implicitWidth height: addressBarDialog.implicitHeight + onShownChanged: addressBarDialog.observeShownChanged(shown); Component.onCompleted: { root.parentChanged.connect(center); center(); @@ -42,11 +45,50 @@ Window { anchors.centerIn = parent; } + function goCard(card) { + addressLine.text = card.userStory.name; + toggleOrGo(true); + } + property var allDomains: []; + property var suggestionChoices: []; + property var domainsBaseUrl: null; + property int cardWidth: 200; + property int cardHeight: 152; + AddressBarDialog { id: addressBarDialog implicitWidth: backgroundImage.width implicitHeight: backgroundImage.height + Row { + width: backgroundImage.width; + anchors { + bottom: backgroundImage.top; + bottomMargin: 2 * hifi.layout.spacing; + right: backgroundImage.right; + rightMargin: -104; // FIXME + } + spacing: hifi.layout.spacing; + Card { + id: s0; + width: cardWidth; + height: cardHeight; + goFunction: goCard + } + Card { + id: s1; + width: cardWidth; + height: cardHeight; + goFunction: goCard + } + Card { + id: s2; + width: cardWidth; + height: cardHeight; + goFunction: goCard + } + } + Image { id: backgroundImage source: "../images/address-bar.svg" @@ -128,31 +170,187 @@ Window { } font.pixelSize: hifi.fonts.pixelSize * root.scale * 0.75 helperText: "Go to: place, @user, /path, network address" + onTextChanged: filterChoicesByText() } } } + function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects. + // TODO: make available to other .qml. + var request = new XMLHttpRequest(); + // QT bug: apparently doesn't handle onload. Workaround using readyState. + request.onreadystatechange = function () { + var READY_STATE_DONE = 4; + var HTTP_OK = 200; + if (request.readyState >= READY_STATE_DONE) { + var error = (request.status !== HTTP_OK) && request.status.toString() + ':' + request.statusText, + response = !error && request.responseText, + contentType = !error && request.getResponseHeader('content-type'); + if (!error && contentType.indexOf('application/json') === 0) { + try { + response = JSON.parse(response); + } catch (e) { + error = e; + } + } + cb(error, response); + } + }; + request.open("GET", url, true); + request.send(); + } + // call iterator(element, icb) once for each element of array, and then cb(error) when icb(error) has been called by each iterator. + // short-circuits if error. Note that iterator MUST be an asynchronous function. (Use setTimeout if necessary.) + function asyncEach(array, iterator, cb) { + var count = array.length; + function icb(error) { + if (!--count || error) { + count = -1; // don't cb multiple times (e.g., if error) + cb(error); + } + } + if (!count) { + return cb(); + } + array.forEach(function (element) { + iterator(element, icb); + }); + } + + function identity(x) { + return x; + } + + function addPictureToDomain(domainInfo, cb) { // asynchronously add thumbnail and lobby to domainInfo, if available, and cb(error) + // This requests data for all the names at once, and just uses the first one to come back. + // We might change this to check one at a time, which would be less requests and more latency. + asyncEach([domainInfo.name].concat(domainInfo.names || null).filter(identity), function (name, icb) { + var url = "https://metaverse.highfidelity.com/api/v1/places/" + name; + getRequest(url, function (error, json) { + var previews = !error && json.data.place.previews; + if (previews) { + if (!domainInfo.thumbnail) { // just grab the first one + domainInfo.thumbnail = previews.thumbnail; + } + if (!domainInfo.lobby) { + domainInfo.lobby = previews.lobby; + } + } + icb(error); + }); + }, cb); + } + + function getDomains(options, cb) { // cb(error, arrayOfData) + if (!options.page) { + options.page = 1; + } + if (!domainsBaseUrl) { + var domainsOptions = [ + 'open', // published hours handle now + 'active', // has at least one person connected. FIXME: really want any place that is verified accessible. + // FIXME: really want places I'm allowed in, not just open ones. + 'restriction=open', // Not by whitelist, etc. FIXME: If logged in, add hifi to the restriction options, in order to include places that require login. + // FIXME add maturity + 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), + 'sort_by=users', + 'sort_order=desc', + ]; + domainsBaseUrl = "https://metaverse.highfidelity.com/api/v1/domains/all?" + domainsOptions.join('&'); + } + var url = domainsBaseUrl + "&page=" + options.page + "&users=" + options.minUsers + "-" + options.maxUsers; + getRequest(url, function (error, json) { + if (!error && (json.status !== 'success')) { + error = new Error("Bad response: " + JSON.stringify(json)); + } + if (error) { + error.message += ' for ' + url; + return cb(error); + } + var domains = json.data.domains; + if (json.current_page < json.total_pages) { + options.page++; + return getDomains(options, function (error, others) { + cb(error, domains.concat(others)); + }); + } + cb(null, domains); + }); + } + + function filterChoicesByText() { + function fill1(target, data) { + if (!data) { + target.visible = false; + return; + } + console.log('suggestion:', JSON.stringify(data)); + target.userStory = data; + target.image.source = data.lobby || target.defaultPicture; + target.placeText = data.name; + target.usersText = data.online_users + ((data.online_users === 1) ? ' user' : ' users'); + target.visible = true; + } + var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity); + var filtered = !words.length ? suggestionChoices : allDomains.filter(function (domain) { + var text = domain.names.concat(domain.tags).join(' '); + if (domain.description) { + text += domain.description; + } + text = text.toUpperCase(); + return words.every(function (word) { + return text.indexOf(word) >= 0; + }); + }); + fill1(s0, filtered[0]); + fill1(s1, filtered[1]); + fill1(s2, filtered[2]); + } + + function fillDestinations() { + allDomains = suggestionChoices = []; + getDomains({minUsers: 0, maxUsers: 20}, function (error, domains) { + if (error) { + console.log('domain query failed:', error); + return filterChoicesByText(); + } + var here = AddressManager.hostname; // don't show where we are now. + allDomains = domains.filter(function (domain) { return domain.name !== here; }); + // Whittle down suggestions to those that have at least one user, and try to get pictures. + suggestionChoices = allDomains.filter(function (domain) { return domain.online_users; }); + asyncEach(domains, addPictureToDomain, function (error) { + if (error) { + console.log('place picture query failed:', error); + } + // Whittle down more by requiring a picture. + suggestionChoices = suggestionChoices.filter(function (domain) { return domain.lobby; }); + filterChoicesByText(); + }); + }); + } + onVisibleChanged: { if (visible) { addressLine.forceActiveFocus() + fillDestinations(); } else { addressLine.text = "" } } - function toggleOrGo() { + function toggleOrGo(fromSuggestions) { if (addressLine.text !== "") { - addressBarDialog.loadAddress(addressLine.text) + addressBarDialog.loadAddress(addressLine.text, fromSuggestions) } - root.visible = false; + root.shown = false; } Keys.onPressed: { switch (event.key) { case Qt.Key_Escape: case Qt.Key_Back: - root.visible = false + root.shown = false event.accepted = true break case Qt.Key_Enter: diff --git a/interface/resources/qml/AssetServer.qml b/interface/resources/qml/AssetServer.qml index 370bc92d81..c9b6305258 100644 --- a/interface/resources/qml/AssetServer.qml +++ b/interface/resources/qml/AssetServer.qml @@ -15,15 +15,15 @@ import Qt.labs.settings 1.0 import "styles-uit" import "controls-uit" as HifiControls -import "windows-uit" +import "windows" import "dialogs" -Window { +ScrollingWindow { id: root objectName: "AssetServer" title: "Asset Browser" resizable: true - destroyOnInvisible: true + destroyOnHidden: true implicitWidth: 384; implicitHeight: 640 minSize: Qt.vector2d(200, 300) @@ -341,7 +341,7 @@ Window { HifiControls.GlyphButton { glyph: hifi.glyphs.reload - color: hifi.buttons.white + color: hifi.buttons.black colorScheme: root.colorScheme width: hifi.dimensions.controlLineHeight @@ -349,8 +349,8 @@ Window { } HifiControls.Button { - text: "ADD TO WORLD" - color: hifi.buttons.white + text: "Add To World" + color: hifi.buttons.black colorScheme: root.colorScheme width: 120 @@ -360,8 +360,8 @@ Window { } HifiControls.Button { - text: "RENAME" - color: hifi.buttons.white + text: "Rename" + color: hifi.buttons.black colorScheme: root.colorScheme width: 80 @@ -372,7 +372,7 @@ Window { HifiControls.Button { id: deleteButton - text: "DELETE" + text: "Delete" color: hifi.buttons.red colorScheme: root.colorScheme width: 80 diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 89ab333a0d..e9b99331c7 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -2,22 +2,26 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 import QtWebEngine 1.1 -import "controls" -import "styles" +import "controls-uit" +import "styles" as HifiStyles +import "styles-uit" import "windows" -Window { +ScrollingWindow { id: root HifiConstants { id: hifi } + HifiStyles.HifiConstants { id: hifistyles } title: "Browser" resizable: true - destroyOnInvisible: true + destroyOnHidden: true width: 800 height: 600 property alias webView: webview - + x: 100 + y: 100 + Component.onCompleted: { - visible = true + shown = true addressBar.text = webview.url } @@ -30,15 +34,9 @@ Window { Item { id:item - anchors.fill: parent - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: webview.top - color: "white" - } - + width: pane.contentWidth + implicitHeight: pane.scrollHeight + Row { id: buttons spacing: 4 @@ -46,25 +44,37 @@ Window { anchors.topMargin: 8 anchors.left: parent.left anchors.leftMargin: 8 - FontAwesome { - id: back; text: "\uf0a8"; size: 48; enabled: webview.canGoBack; - color: enabled ? hifi.colors.text : hifi.colors.disabledText + HiFiGlyphs { + id: back; + enabled: webview.canGoBack; + text: hifi.glyphs.backward + color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText + size: 48 MouseArea { anchors.fill: parent; onClicked: webview.goBack() } } - FontAwesome { - id: forward; text: "\uf0a9"; size: 48; enabled: webview.canGoForward; - color: enabled ? hifi.colors.text : hifi.colors.disabledText - MouseArea { anchors.fill: parent; onClicked: webview.goBack() } + + HiFiGlyphs { + id: forward; + enabled: webview.canGoForward; + text: hifi.glyphs.forward + color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText + size: 48 + MouseArea { anchors.fill: parent; onClicked: webview.goForward() } } - FontAwesome { - id: reload; size: 48; text: webview.loading ? "\uf057" : "\uf021" - MouseArea { anchors.fill: parent; onClicked: webview.loading ? webview.stop() : webview.reload() } + + HiFiGlyphs { + id: reload; + enabled: webview.canGoForward; + text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload + color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText + size: 48 + MouseArea { anchors.fill: parent; onClicked: webview.goForward() } } } - Border { + Item { + id: border height: 48 - radius: 8 anchors.top: parent.top anchors.topMargin: 8 anchors.right: parent.right @@ -86,15 +96,18 @@ Window { onSourceChanged: console.log("Icon url: " + source) } } - - TextInput { + + TextField { id: addressBar anchors.right: parent.right anchors.rightMargin: 8 anchors.left: barIcon.right anchors.leftMargin: 0 anchors.verticalCenter: parent.verticalCenter - + focus: true + colorScheme: hifi.colorSchemes.dark + placeholderText: "Enter URL" + Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i") Keys.onPressed: { switch(event.key) { case Qt.Key_Enter: @@ -110,7 +123,7 @@ Window { } } - WebView { + WebEngineView { id: webview url: "http://highfidelity.com" anchors.top: buttons.bottom @@ -119,7 +132,7 @@ Window { anchors.left: parent.left anchors.right: parent.right onLoadingChanged: { - if (loadRequest.status == WebEngineView.LoadSucceededStatus) { + if (loadRequest.status === WebEngineView.LoadSucceededStatus) { addressBar.text = loadRequest.url } } @@ -127,7 +140,7 @@ Window { console.log("New icon: " + icon) } - profile: desktop.browserProfile + //profile: desktop.browserProfile } diff --git a/interface/resources/qml/InfoView.qml b/interface/resources/qml/InfoView.qml index c5dba7e1f3..f18969fb2f 100644 --- a/interface/resources/qml/InfoView.qml +++ b/interface/resources/qml/InfoView.qml @@ -12,9 +12,9 @@ import QtQuick 2.5 import Hifi 1.0 as Hifi import "controls-uit" -import "windows-uit" as Windows +import "windows" as Windows -Windows.Window { +Windows.ScrollingWindow { id: root width: 800 height: 800 diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 1b25b75608..f75e83e36e 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -14,7 +14,7 @@ import "controls" import "styles" import "windows" -Window { +ScrollingWindow { id: root HifiConstants { id: hifi } objectName: "LoginDialog" @@ -22,8 +22,9 @@ Window { width: loginDialog.implicitWidth // FIXME make movable anchors.centerIn: parent - destroyOnInvisible: false - visible: false + destroyOnHidden: false + hideBackground: true + shown: false LoginDialog { id: loginDialog @@ -268,8 +269,8 @@ Window { } } - onVisibleChanged: { - if (!visible) { + onShownChanged: { + if (!shown) { username.text = "" password.text = "" loginDialog.statusText = "" @@ -282,7 +283,7 @@ Window { switch (event.key) { case Qt.Key_Escape: case Qt.Key_Back: - root.visible = false; + root.shown = false; event.accepted = true; break; diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index ae052879db..542b44b95e 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -13,16 +13,16 @@ import QtQuick.Controls 1.4 import QtWebEngine 1.1 import QtWebChannel 1.0 -import "windows-uit" as Windows +import "windows" as Windows import "controls-uit" as Controls import "styles-uit" -Windows.Window { +Windows.ScrollingWindow { id: root HifiConstants { id: hifi } title: "WebWindow" resizable: true - visible: false + shown: false // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property alias source: webview.url diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 0420cd2e88..7be747a3ad 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -14,7 +14,7 @@ Windows.Window { HifiConstants { id: hifi } title: "QmlWindow" resizable: true - visible: false + shown: false focus: true property var channel; // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index aaff43b146..f6a4600e06 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -15,18 +15,18 @@ import QtWebEngine 1.1 import QtWebChannel 1.0 import Qt.labs.settings 1.0 -import "windows-uit" +import "windows" import "controls-uit" import "styles-uit" -Window { +ScrollingWindow { id: toolWindow resizable: true objectName: "ToolWindow" destroyOnCloseButton: false - destroyOnInvisible: false + destroyOnHidden: false closable: true - visible: false + shown: false title: "Edit" property alias tabView: tabView implicitWidth: 520; implicitHeight: 695 @@ -137,12 +137,14 @@ Window { } function updateVisiblity() { - for (var i = 0; i < tabView.count; ++i) { - if (tabView.getTab(i).enabled) { - return; + if (visible) { + for (var i = 0; i < tabView.count; ++i) { + if (tabView.getTab(i).enabled) { + return; + } } + shown = false; } - visible = false; } function findIndexForUrl(source) { @@ -172,7 +174,7 @@ Window { var tab = tabView.getTab(index); if (newVisible) { - toolWindow.visible = true + toolWindow.shown = true tab.enabled = true } else { tab.enabled = false; diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml index 4cb5b206c6..ca3a2da577 100644 --- a/interface/resources/qml/UpdateDialog.qml +++ b/interface/resources/qml/UpdateDialog.qml @@ -3,13 +3,16 @@ import QtQuick 2.3 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 import QtGraphicalEffects 1.0 -import "controls" -import "styles" + +import "controls-uit" +import "styles" as HifiStyles +import "styles-uit" import "windows" -Window { +ScrollingWindow { id: root HifiConstants { id: hifi } + HifiStyles.HifiConstants { id: hifistyles } objectName: "UpdateDialog" width: updateDialog.implicitWidth height: updateDialog.implicitHeight @@ -40,22 +43,6 @@ Window { width: updateDialog.contentWidth + updateDialog.borderWidth * 2 height: mainContent.height + updateDialog.borderWidth * 2 - updateDialog.closeMargin / 2 - - MouseArea { - width: parent.width - height: parent.height - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter - } - drag { - target: root - minimumX: 0 - minimumY: 0 - maximumX: root.parent ? root.maximumX : 0 - maximumY: root.parent ? root.maximumY : 0 - } - } } Image { @@ -89,7 +76,7 @@ Window { text: "Update Available" font { family: updateDialog.fontFamily - pixelSize: hifi.fonts.pixelSize * 1.5 + pixelSize: hifistyles.fonts.pixelSize * 1.5 weight: Font.DemiBold } color: "#303030" @@ -100,10 +87,10 @@ Window { text: updateDialog.updateAvailableDetails font { family: updateDialog.fontFamily - pixelSize: hifi.fonts.pixelSize * 0.6 + pixelSize: hifistyles.fonts.pixelSize * 0.6 letterSpacing: -0.5 } - color: hifi.colors.text + color: hifistyles.colors.text anchors { top: updateAvailable.bottom } @@ -130,12 +117,12 @@ Window { Text { id: releaseNotes wrapMode: Text.Wrap - width: parent.width - updateDialog.closeMargin + width: parent.parent.width - updateDialog.closeMargin text: updateDialog.releaseNotes - color: hifi.colors.text + color: hifistyles.colors.text font { family: updateDialog.fontFamily - pixelSize: hifi.fonts.pixelSize * 0.65 + pixelSize: hifistyles.fonts.pixelSize * 0.65 } } } @@ -157,7 +144,7 @@ Window { color: "#0c9ab4" // Same as logo font { family: updateDialog.fontFamily - pixelSize: hifi.fonts.pixelSize * 1.2 + pixelSize: hifistyles.fonts.pixelSize * 1.2 weight: Font.DemiBold } anchors { @@ -169,7 +156,7 @@ Window { MouseArea { id: cancelButtonAction anchors.fill: parent - onClicked: updateDialog.closeDialog() + onClicked: root.shown = false cursorShape: "PointingHandCursor" } } @@ -185,7 +172,7 @@ Window { color: "#0c9ab4" // Same as logo font { family: updateDialog.fontFamily - pixelSize: hifi.fonts.pixelSize * 1.2 + pixelSize: hifistyles.fonts.pixelSize * 1.2 weight: Font.DemiBold } anchors { diff --git a/interface/resources/qml/controls-uit/AttachmentsTable.qml b/interface/resources/qml/controls-uit/AttachmentsTable.qml index 6022de7f93..7d0280b72d 100644 --- a/interface/resources/qml/controls-uit/AttachmentsTable.qml +++ b/interface/resources/qml/controls-uit/AttachmentsTable.qml @@ -15,13 +15,12 @@ import QtQuick.XmlListModel 2.0 import "../styles-uit" import "../controls-uit" as HifiControls -import "../windows-uit" +import "../windows" import "../hifi/models" TableView { id: tableView - // property var tableModel: ListModel { } property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light @@ -46,7 +45,7 @@ TableView { RalewayRegular { id: textHeader - size: hifi.fontSizes.tableText + size: hifi.fontSizes.tableHeading color: hifi.colors.lightGrayText text: styleData.value anchors { @@ -87,7 +86,7 @@ TableView { bottomMargin: 3 // "" } radius: 3 - color: hifi.colors.tableScrollHandle + color: hifi.colors.tableScrollHandleDark } } @@ -107,7 +106,7 @@ TableView { margins: 1 // Shrink } radius: 4 - color: hifi.colors.tableScrollBackground + color: hifi.colors.tableScrollBackgroundDark } } diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml old mode 100755 new mode 100644 index f99e18b12b..df3210a20d --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -91,7 +91,7 @@ FocusScope { HiFiGlyphs { anchors { top: parent.top - topMargin: -8 + topMargin: -11 horizontalCenter: parent.horizontalCenter } size: hifi.dimensions.spinnerSize @@ -199,7 +199,7 @@ FocusScope { anchors.leftMargin: hifi.dimensions.textPadding anchors.verticalCenter: parent.verticalCenter id: popupText - text: listView.model[index] + text: listView.model[index] ? listView.model[index] : "" size: hifi.fontSizes.textFieldInput color: hifi.colors.baseGray } diff --git a/interface/resources/qml/controls-uit/GlyphButton.qml b/interface/resources/qml/controls-uit/GlyphButton.qml index c4ee53c04f..ac353b5a52 100644 --- a/interface/resources/qml/controls-uit/GlyphButton.qml +++ b/interface/resources/qml/controls-uit/GlyphButton.qml @@ -17,8 +17,9 @@ import "../styles-uit" Original.Button { property int color: 0 - property int colorScheme: hifi.colorShemes.light + property int colorScheme: hifi.colorSchemes.light property string glyph: "" + property int size: 32 width: 120 height: 28 @@ -65,7 +66,13 @@ Original.Button { : hifi.buttons.disabledTextColor[control.colorScheme] verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter + anchors { + // Tweak horizontal alignment so that it looks right. + left: parent.left + leftMargin: -0.5 + } text: control.glyph + size: control.size } } } diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml old mode 100755 new mode 100644 index 599d94c28d..a1237d4bc7 --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -36,15 +36,15 @@ SpinBox { id: spinStyle background: Rectangle { color: isLightColorScheme - ? (spinBox.focus ? hifi.colors.white : hifi.colors.lightGray) - : (spinBox.focus ? hifi.colors.black : hifi.colors.baseGrayShadow) + ? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray) + : (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow) border.color: spinBoxLabelInside.visible ? spinBoxLabelInside.color : hifi.colors.primaryHighlight - border.width: spinBox.focus ? spinBoxLabelInside.visible ? 2 : 1 : 0 + border.width: spinBox.activeFocus ? spinBoxLabelInside.visible ? 2 : 1 : 0 } textColor: isLightColorScheme - ? (spinBox.focus ? hifi.colors.black : hifi.colors.lightGray) - : (spinBox.focus ? hifi.colors.white : hifi.colors.lightGrayText) + ? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray) + : (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText) selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight @@ -56,7 +56,7 @@ SpinBox { incrementControl: HiFiGlyphs { id: incrementButton text: hifi.glyphs.caratUp - x: 6 + x: 10 y: 1 size: hifi.dimensions.spinnerSize color: styleData.upPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray @@ -64,8 +64,8 @@ SpinBox { decrementControl: HiFiGlyphs { text: hifi.glyphs.caratDn - x: 6 - y: -3 + x: 10 + y: -1 size: hifi.dimensions.spinnerSize color: styleData.downPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray } @@ -96,7 +96,7 @@ SpinBox { anchors.fill: parent propagateComposedEvents: true onWheel: { - if(spinBox.focus) + if(spinBox.activeFocus) wheel.accepted = false else wheel.accepted = true diff --git a/interface/resources/qml/controls-uit/Table.qml b/interface/resources/qml/controls-uit/Table.qml index de6950c07e..35029ad8bf 100644 --- a/interface/resources/qml/controls-uit/Table.qml +++ b/interface/resources/qml/controls-uit/Table.qml @@ -17,20 +17,68 @@ import "../styles-uit" TableView { id: tableView - property var tableModel: ListModel { } property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property bool expandSelectedRow: false - model: tableModel - - TableViewColumn { - role: "name" - } - - anchors { left: parent.left; right: parent.right } + model: ListModel { } headerVisible: false - headerDelegate: Item { } // Fix OSX QML bug that displays scrollbar starting too low. + headerDelegate: Rectangle { + height: hifi.dimensions.tableHeaderHeight + color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + + RalewayRegular { + id: titleText + text: styleData.value + size: hifi.fontSizes.tableHeading + font.capitalization: Font.AllUppercase + color: hifi.colors.baseGrayHighlight + anchors { + left: parent.left + leftMargin: hifi.dimensions.tablePadding + verticalCenter: parent.verticalCenter + } + } + + HiFiGlyphs { + id: titleSort + text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn + color: hifi.colors.baseGrayHighlight + size: hifi.fontSizes.tableHeadingIcon + anchors { + left: titleText.right + leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 + right: parent.right + rightMargin: hifi.dimensions.tablePadding + verticalCenter: titleText.verticalCenter + } + visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column + } + + Rectangle { + width: 1 + anchors { + left: parent.left + top: parent.top + topMargin: 1 + bottom: parent.bottom + bottomMargin: 2 + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + visible: styleData.column > 0 + } + + Rectangle { + height: 1 + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + } + } // Use rectangle to draw border with rounded corners. frameVisible: false @@ -50,8 +98,10 @@ TableView { style: TableViewStyle { // Needed in order for rows to keep displaying rows after end of table entries. - backgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightEven : hifi.colors.tableRowDarkEven - alternateBackgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd + backgroundColor: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + alternateBackgroundColor: tableView.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd + + padding.top: headerVisible ? hifi.dimensions.tableHeaderHeight: 0 handle: Item { id: scrollbarHandle @@ -59,33 +109,38 @@ TableView { Rectangle { anchors { fill: parent + topMargin: 3 + bottomMargin: 3 // "" leftMargin: 2 // Move it right rightMargin: -2 // "" - topMargin: 3 // Shrink vertically - bottomMargin: 3 // "" } radius: 3 - color: hifi.colors.tableScrollHandle + color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark } } scrollBarBackground: Item { - implicitWidth: 10 + implicitWidth: 9 Rectangle { anchors { fill: parent margins: -1 // Expand + topMargin: headerVisible ? -hifi.dimensions.tableHeaderHeight : -1 } - color: hifi.colors.baseGrayHighlight - } + color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark - Rectangle { - anchors { - fill: parent - margins: 1 // Shrink + Rectangle { + // Extend header bottom border + anchors { + top: parent.top + topMargin: hifi.dimensions.tableHeaderHeight - 1 + left: parent.left + right: parent.right + } + height: 1 + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + visible: headerVisible } - radius: 4 - color: hifi.colors.tableScrollBackground } } @@ -99,85 +154,11 @@ TableView { } rowDelegate: Rectangle { - height: (styleData.selected ? 1.8 : 1) * hifi.dimensions.tableRowHeight + height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight color: styleData.selected ? hifi.colors.primaryHighlight : tableView.isLightColorScheme ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) } - - itemDelegate: Item { - anchors { - left: parent ? parent.left : undefined - leftMargin: hifi.dimensions.tablePadding - right: parent ? parent.right : undefined - rightMargin: hifi.dimensions.tablePadding - } - - FiraSansSemiBold { - id: textItem - text: styleData.value - size: hifi.fontSizes.tableText - color: colorScheme == hifi.colorSchemes.light - ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) - : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - anchors { - left: parent.left - right: parent.right - top: parent.top - topMargin: 3 - } - - // FIXME: Put reload item in tableModel passed in from RunningScripts. - HiFiGlyphs { - id: reloadButton - text: hifi.glyphs.reloadSmall - color: reloadButtonArea.pressed ? hifi.colors.white : parent.color - anchors { - top: parent.top - right: stopButton.left - verticalCenter: parent.verticalCenter - } - MouseArea { - id: reloadButtonArea - anchors { fill: parent; margins: -2 } - onClicked: reloadScript(model.url) - } - } - - // FIXME: Put stop item in tableModel passed in from RunningScripts. - HiFiGlyphs { - id: stopButton - text: hifi.glyphs.closeSmall - color: stopButtonArea.pressed ? hifi.colors.white : parent.color - anchors { - top: parent.top - right: parent.right - verticalCenter: parent.verticalCenter - } - MouseArea { - id: stopButtonArea - anchors { fill: parent; margins: -2 } - onClicked: stopScript(model.url) - } - } - } - - // FIXME: Automatically use aux. information from tableModel - FiraSansSemiBold { - text: tableModel.get(styleData.row) ? tableModel.get(styleData.row).url : "" - elide: Text.ElideMiddle - size: hifi.fontSizes.tableText - color: colorScheme == hifi.colorSchemes.light - ? (styleData.selected ? hifi.colors.black : hifi.colors.lightGray) - : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - anchors { - top: textItem.bottom - left: parent.left - right: parent.right - } - visible: styleData.selected - } - } } diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index 79027cdc3b..65fab00700 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -36,14 +36,14 @@ TextField { style: TextFieldStyle { textColor: isLightColorScheme - ? (textField.focus ? hifi.colors.black : hifi.colors.lightGray) - : (textField.focus ? hifi.colors.white : hifi.colors.lightGrayText) + ? (textField.activeFocus ? hifi.colors.black : hifi.colors.lightGray) + : (textField.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText) background: Rectangle { color: isLightColorScheme - ? (textField.focus ? hifi.colors.white : hifi.colors.textFieldLightBackground) - : (textField.focus ? hifi.colors.black : hifi.colors.baseGrayShadow) + ? (textField.activeFocus ? hifi.colors.white : hifi.colors.textFieldLightBackground) + : (textField.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow) border.color: hifi.colors.primaryHighlight - border.width: textField.focus ? 1 : 0 + border.width: textField.activeFocus ? 1 : 0 radius: isSearchField ? textField.height / 2 : 0 HiFiGlyphs { diff --git a/interface/resources/qml/controls-uit/Tree.qml b/interface/resources/qml/controls-uit/Tree.qml index 6d4d202e2c..aa1d10f030 100644 --- a/interface/resources/qml/controls-uit/Tree.qml +++ b/interface/resources/qml/controls-uit/Tree.qml @@ -1,5 +1,5 @@ // -// Table.qml +// Tree.qml // // Created by David Rowe on 17 Feb 2016 // Copyright 2016 High Fidelity, Inc. @@ -85,27 +85,18 @@ TreeView { bottomMargin: 3 // "" } radius: 3 - color: hifi.colors.tableScrollHandle + color: hifi.colors.tableScrollHandleDark } } scrollBarBackground: Item { - implicitWidth: 10 + implicitWidth: 9 Rectangle { anchors { fill: parent margins: -1 // Expand } - color: hifi.colors.baseGrayHighlight - } - - Rectangle { - anchors { - fill: parent - margins: 1 // Shrink - } - radius: 4 - color: hifi.colors.tableScrollBackground + color: hifi.colors.tableBackgroundDark } } diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 62a72e3d8c..e5ff849df8 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -24,6 +24,15 @@ FocusScope { readonly property int invalid_position: -9999; property rect recommendedRect: Qt.rect(0,0,0,0); property var expectedChildren; + property bool repositionLocked: true + property bool hmdHandMouseActive: false + + onRepositionLockedChanged: { + if (!repositionLocked) { + d.handleSizeChanged(); + } + + } onHeightChanged: d.handleSizeChanged(); @@ -56,6 +65,9 @@ FocusScope { id: d function handleSizeChanged() { + if (desktop.repositionLocked) { + return; + } var oldRecommendedRect = recommendedRect; var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedOverlayRect(); var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, @@ -64,7 +76,7 @@ FocusScope { var oldChildren = expectedChildren; var newChildren = d.getRepositionChildren(); - if (oldRecommendedRect != Qt.rect(0,0,0,0) + if (oldRecommendedRect != Qt.rect(0,0,0,0) && oldRecommendedRect != Qt.rect(0,0,1,1) && (oldRecommendedRect != newRecommendedRect || oldChildren != newChildren) ) { @@ -93,6 +105,17 @@ FocusScope { return item; } + function findMatchingChildren(item, predicate) { + var results = []; + for (var i in item.children) { + var child = item.children[i]; + if (predicate(child)) { + results.push(child); + } + } + return results; + } + function isTopLevelWindow(item) { return item.topLevelWindow; } @@ -106,19 +129,9 @@ FocusScope { } function getTopLevelWindows(predicate) { - var currentWindows = []; - if (!desktop) { - console.log("Could not find desktop for " + item) - return currentWindows; - } - - for (var i = 0; i < desktop.children.length; ++i) { - var child = desktop.children[i]; - if (isTopLevelWindow(child) && (!predicate || predicate(child))) { - currentWindows.push(child) - } - } - return currentWindows; + return findMatchingChildren(desktop, function(child) { + return (isTopLevelWindow(child) && (!predicate || predicate(child))); + }); } function getDesktopWindow(item) { @@ -227,22 +240,16 @@ FocusScope { } function getRepositionChildren(predicate) { - var currentWindows = []; - if (!desktop) { - console.log("Could not find desktop"); - return currentWindows; - } - - for (var i = 0; i < desktop.children.length; ++i) { - var child = desktop.children[i]; - if (child.shouldReposition === true && (!predicate || predicate(child))) { - currentWindows.push(child) - } - } - return currentWindows; + return findMatchingChildren(desktop, function(child) { + return (child.shouldReposition === true && (!predicate || predicate(child))); + }); } function repositionAll() { + if (desktop.repositionLocked) { + return; + } + var oldRecommendedRect = recommendedRect; var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; var newRecommendedRect = Controller.getRecommendedOverlayRect(); @@ -265,6 +272,63 @@ FocusScope { } } + property bool pinned: false + property var hiddenChildren: [] + + function togglePinned() { + pinned = !pinned + } + + function setPinned(newPinned) { + pinned = newPinned + } + + property real unpinnedAlpha: 1.0; + + Behavior on unpinnedAlpha { + NumberAnimation { + easing.type: Easing.Linear; + duration: 300 + } + } + + state: "NORMAL" + states: [ + State { + name: "NORMAL" + PropertyChanges { target: desktop; unpinnedAlpha: 1.0 } + }, + State { + name: "PINNED" + PropertyChanges { target: desktop; unpinnedAlpha: 0.0 } + } + ] + + transitions: [ + Transition { + NumberAnimation { properties: "unpinnedAlpha"; duration: 300 } + } + ] + + onPinnedChanged: { + if (pinned) { + desktop.focus = true; + desktop.forceActiveFocus(); + + // recalculate our non-pinned children + hiddenChildren = d.findMatchingChildren(desktop, function(child){ + return !d.isTopLevelWindow(child) && child.visible && !child.pinned; + }); + + hiddenChildren.forEach(function(child){ + child.opacity = Qt.binding(function(){ return desktop.unpinnedAlpha }); + }); + } + state = pinned ? "PINNED" : "NORMAL" + } + + onShowDesktop: pinned = false + function raise(item) { var targetWindow = d.getDesktopWindow(item); if (!targetWindow) { @@ -422,13 +486,28 @@ FocusScope { event.accepted = false; } - function unfocusWindows() { + // First find the active focus item, and unfocus it, all the way + // up the parent chain to the window + var currentFocus = offscreenWindow.activeFocusItem; + var targetWindow = d.getDesktopWindow(currentFocus); + while (currentFocus) { + if (currentFocus === targetWindow) { + break; + } + currentFocus.focus = false; + currentFocus = currentFocus.parent; + } + + // Unfocus all windows var windows = d.getTopLevelWindows(); for (var i = 0; i < windows.length; ++i) { windows[i].focus = false; } + + // For the desktop to have active focus desktop.focus = true; + desktop.forceActiveFocus(); } FocusHack { id: focusHack; } @@ -446,5 +525,5 @@ FocusScope { enabled: DebugQML onTriggered: focusDebugger.visible = !focusDebugger.visible } - + } diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 916ef434b6..fa5be18cd3 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -1,4 +1,14 @@ -import QtQuick 2.0 +// +// FileDialog.qml +// +// Created by Bradley Austin Davis on 14 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 import QtQuick.Controls 1.4 import Qt.labs.folderlistmodel 2.1 import Qt.labs.settings 1.0 @@ -6,17 +16,23 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import ".." +import "../controls-uit" +import "../styles-uit" import "../windows" -import "../styles" -import "../controls" as VrControls + import "fileDialog" //FIXME implement shortcuts for favorite location ModalWindow { id: root resizable: true - width: 640 - height: 480 + implicitWidth: 480 + implicitHeight: 360 + + minSize: Qt.vector2d(360, 240) + draggable: true + + HifiConstants { id: hifi } Settings { category: "FileDialog" @@ -30,12 +46,14 @@ ModalWindow { // Set from OffscreenUi::getOpenFile() property alias caption: root.title; // Set from OffscreenUi::getOpenFile() - property alias dir: model.folder; + property alias dir: fileTableModel.folder; // Set from OffscreenUi::getOpenFile() property alias filter: selectionType.filtersString; // Set from OffscreenUi::getOpenFile() property int options; // <-- FIXME unused + property string iconText: text !== "" ? hifi.glyphs.scriptUpload : "" + property int iconSize: 40 property bool selectDirectory: false; property bool showHidden: false; @@ -46,77 +64,141 @@ ModalWindow { property alias model: fileTableView.model property var drives: helper.drives() + property int titleWidth: 0 + signal selectedFile(var file); signal canceled(); Component.onCompleted: { console.log("Helper " + helper + " drives " + drives) - drivesSelector.onCurrentTextChanged.connect(function(){ - root.dir = helper.pathToUrl(drivesSelector.currentText); - }) + + // HACK: The following lines force the model to initialize properly such that the go-up button + // works properly from the initial screen. + var initialFolder = folderListModel.folder; + fileTableModel.folder = helper.pathToUrl(drives[0]); + fileTableModel.folder = initialFolder; + + iconText = root.title !== "" ? hifi.glyphs.scriptUpload : ""; + + // Clear selection when click on external frame. + frameClicked.connect(function() { d.clearSelection(); }); + + if (selectDirectory) { + currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder)); + } + + fileTableView.forceActiveFocus(); } - Rectangle { - anchors.fill: parent - color: "white" + Item { + clip: true + width: pane.width + height: pane.height + anchors.margins: 0 + + MouseArea { + // Clear selection when click on internal unused area. + anchors.fill: parent + drag.target: root + onClicked: d.clearSelection() + } Row { id: navControls - anchors { left: parent.left; top: parent.top; margins: 8 } - spacing: 8 - // FIXME implement back button - //VrControls.ButtonAwesome { - // id: backButton - // text: "\uf0a8" - // size: currentDirectory.height - // enabled: d.backStack.length != 0 - // MouseArea { anchors.fill: parent; onClicked: d.navigateBack() } - //} - VrControls.ButtonAwesome { + anchors { + top: parent.top + topMargin: hifi.dimensions.contentMargin.y + left: parent.left + } + spacing: hifi.dimensions.contentSpacing.x + + GlyphButton { id: upButton - enabled: model.parentFolder && model.parentFolder !== "" - text: "\uf0aa" - size: 32 + glyph: hifi.glyphs.levelUp + width: height + size: 30 + enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== "" onClicked: d.navigateUp(); } - VrControls.ButtonAwesome { + + GlyphButton { id: homeButton property var destination: helper.home(); + glyph: hifi.glyphs.home + size: 28 + width: height enabled: d.homeDestination ? true : false - text: "\uf015" - size: 32 onClicked: d.navigateHome(); } - - VrControls.ComboBox { - id: drivesSelector - width: 48 - height: homeButton.height - model: drives - visible: drives.length > 1 - currentIndex: 0 - - } } - TextField { - id: currentDirectory - height: homeButton.height - style: TextFieldStyle { renderType: Text.QtRendering } - anchors { left: navControls.right; right: parent.right; top: parent.top; margins: 8 } - property var lastValidFolder: helper.urlToPath(model.folder) - onLastValidFolderChanged: text = lastValidFolder; - verticalAlignment: Text.AlignVCenter - font.pointSize: 14 - font.bold: true + ComboBox { + id: pathSelector + anchors { + top: parent.top + topMargin: hifi.dimensions.contentMargin.y + left: navControls.right + leftMargin: hifi.dimensions.contentSpacing.x + right: parent.right + } - // FIXME add support auto-completion - onAccepted: { - if (!helper.validFolder(text)) { - text = lastValidFolder; - return + property var lastValidFolder: helper.urlToPath(fileTableModel.folder) + + function calculatePathChoices(folder) { + var folders = folder.split("/"), + choices = [], + i, length; + + if (folders[folders.length - 1] === "") { + folders.pop(); + } + + choices.push(folders[0]); + + for (i = 1, length = folders.length; i < length; i++) { + choices.push(choices[i - 1] + "/" + folders[i]); + } + + if (folders[0] === "") { + // Special handling for OSX root dir. + choices[0] = "/"; + } + + choices.reverse(); + + if (drives && drives.length > 1) { + choices.push("This PC"); + } + + if (choices.length > 0) { + pathSelector.model = choices; + } + } + + onLastValidFolderChanged: { + var folder = d.capitalizeDrive(lastValidFolder); + calculatePathChoices(folder); + } + + onCurrentTextChanged: { + var folder = currentText; + + if (/^[a-zA-z]:$/.test(folder)) { + folder = "file:///" + folder + "/"; + } else if (folder === "This PC") { + folder = "file:///"; + } else { + folder = helper.pathToUrl(folder); + } + + if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) { + if (root.selectDirectory) { + currentSelection.text = currentText !== "This PC" ? currentText : ""; + d.currentSelectionUrl = helper.pathToUrl(currentText); + } + fileTableModel.folder = folder; + fileTableView.forceActiveFocus(); } - model.folder = helper.pathToUrl(text); } } @@ -127,67 +209,329 @@ ModalWindow { property bool currentSelectionIsFolder; property var backStack: [] property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); } - property var modelConnection: Connections { target: model; onFolderChanged: d.update(); } + property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); } property var homeDestination: helper.home(); - Component.onCompleted: update(); + + function capitalizeDrive(path) { + // Consistently capitalize drive letter for Windows. + if (/[a-zA-Z]:/.test(path)) { + return path.charAt(0).toUpperCase() + path.slice(1); + } + return path; + } function update() { var row = fileTableView.currentRow; - if (row === -1 && root.selectDirectory) { - currentSelectionUrl = fileTableView.model.folder; - currentSelectionIsFolder = true; + + if (row === -1) { + if (!root.selectDirectory) { + currentSelection.text = ""; + currentSelectionIsFolder = false; + } return; } - currentSelectionUrl = fileTableView.model.get(row, "fileURL"); + currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath); currentSelectionIsFolder = fileTableView.model.isFolder(row); if (root.selectDirectory || !currentSelectionIsFolder) { - currentSelection.text = helper.urlToPath(currentSelectionUrl); + currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl)); } else { - currentSelection.text = "" + currentSelection.text = ""; } } function navigateUp() { - if (model.parentFolder && model.parentFolder !== "") { - model.folder = model.parentFolder + if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") { + fileTableModel.folder = fileTableModel.parentFolder; return true; } } function navigateHome() { - model.folder = homeDestination; + fileTableModel.folder = homeDestination; return true; } + + function clearSelection() { + fileTableView.selection.clear(); + fileTableView.currentRow = -1; + update(); + } } - FileTableView { + FolderListModel { + id: folderListModel + nameFilters: selectionType.currentFilter + showDirsFirst: true + showDotAndDotDot: false + showFiles: !root.selectDirectory + Component.onCompleted: { + showFiles = !root.selectDirectory + } + + onFolderChanged: { + fileTableModel.update(); // Update once the data from the folder change is available. + } + + function getItem(index, field) { + return get(index, field); + } + } + + ListModel { + // Emulates FolderListModel but contains drive data. + id: driveListModel + + property int count: 1 + + Component.onCompleted: initialize(); + + function initialize() { + var drive, + i; + + count = drives.length; + + for (i = 0; i < count; i++) { + drive = drives[i].slice(0, -1); // Remove trailing "/". + append({ + fileName: drive, + fileModified: new Date(0), + fileSize: 0, + filePath: drive + "/", + fileIsDir: true, + fileNameSort: drive.toLowerCase() + }); + } + } + + function getItem(index, field) { + return get(index)[field]; + } + } + + ListModel { + id: fileTableModel + + // FolderListModel has a couple of problems: + // 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757 + // 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901 + // + // To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with + // drive information when viewing at the computer level. + + property var folder + property int sortOrder: Qt.AscendingOrder + property int sortColumn: 0 + property var model: folderListModel + property string parentFolder: calculateParentFolder(); + + readonly property string rootFolder: "file:///" + + function calculateParentFolder() { + if (model === folderListModel) { + if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) { + return rootFolder; + } else { + return folderListModel.parentFolder; + } + } else { + return ""; + } + } + + onFolderChanged: { + if (folder === rootFolder) { + model = driveListModel; + update(); + } else { + var needsUpdate = model === driveListModel && folder === folderListModel.folder; + + model = folderListModel; + folderListModel.folder = folder; + + if (needsUpdate) { + update(); + } + } + } + + function isFolder(row) { + if (row === -1) { + return false; + } + return get(row).fileIsDir; + } + + function update() { + var dataFields = ["fileName", "fileModified", "fileSize"], + sortFields = ["fileNameSort", "fileModified", "fileSize"], + dataField = dataFields[sortColumn], + sortField = sortFields[sortColumn], + sortValue, + fileName, + fileIsDir, + comparisonFunction, + lower, + middle, + upper, + rows = 0, + i; + + clear(); + + comparisonFunction = sortOrder === Qt.AscendingOrder + ? function(a, b) { return a < b; } + : function(a, b) { return a > b; } + + for (i = 0; i < model.count; i++) { + fileName = model.getItem(i, "fileName"); + fileIsDir = model.getItem(i, "fileIsDir"); + + sortValue = model.getItem(i, dataField); + if (dataField === "fileName") { + // Directories first by prefixing a "*". + // Case-insensitive. + sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase(); + } + + lower = 0; + upper = rows; + while (lower < upper) { + middle = Math.floor((lower + upper) / 2); + var lessThan; + if (comparisonFunction(sortValue, get(middle)[sortField])) { + lessThan = true; + upper = middle; + } else { + lessThan = false; + lower = middle + 1; + } + } + + insert(lower, { + fileName: fileName, + fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), + fileSize: model.getItem(i, "fileSize"), + filePath: model.getItem(i, "filePath"), + fileIsDir: fileIsDir, + fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase() + }); + + rows++; + } + + d.clearSelection(); + } + } + + Table { id: fileTableView - anchors { left: parent.left; right: parent.right; top: currentDirectory.bottom; bottom: currentSelection.top; margins: 8 } + colorScheme: hifi.colorSchemes.light + anchors { + top: navControls.bottom + topMargin: hifi.dimensions.contentSpacing.y + left: parent.left + right: parent.right + bottom: currentSelection.top + bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height + } + headerVisible: !selectDirectory onDoubleClicked: navigateToRow(row); focus: true Keys.onReturnPressed: navigateToCurrentRow(); Keys.onEnterPressed: navigateToCurrentRow(); - model: FolderListModel { - id: model - nameFilters: selectionType.currentFilter - showDirsFirst: true - showDotAndDotDot: false - showFiles: !root.selectDirectory - // For some reason, declaring these bindings directly in the targets doesn't - // work for setting the initial state - Component.onCompleted: { - currentDirectory.lastValidFolder = Qt.binding(function() { return helper.urlToPath(model.folder); }); - upButton.enabled = Qt.binding(function() { return (model.parentFolder && model.parentFolder != "") ? true : false; }); - showFiles = !root.selectDirectory - } - onFolderChanged: { - fileTableView.selection.clear(); - fileTableView.selection.select(0); - fileTableView.currentRow = 0; + + sortIndicatorColumn: 0 + sortIndicatorOrder: Qt.AscendingOrder + sortIndicatorVisible: true + + model: fileTableModel + + function updateSort() { + model.sortOrder = sortIndicatorOrder; + model.sortColumn = sortIndicatorColumn; + model.update(); + } + + onSortIndicatorColumnChanged: { updateSort(); } + + onSortIndicatorOrderChanged: { updateSort(); } + + itemDelegate: Item { + clip: true + + FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } + FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; } + + FiraSansSemiBold { + text: getText(); + elide: styleData.elideMode + anchors { + left: parent.left + leftMargin: hifi.dimensions.tablePadding + right: parent.right + rightMargin: hifi.dimensions.tablePadding + verticalCenter: parent.verticalCenter + } + size: hifi.fontSizes.tableText + color: hifi.colors.baseGrayHighlight + font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir) + ? firaSansSemiBold.name : firaSansRegular.name + + function getText() { + if (styleData.row === -1) { + return styleData.value; + } + + switch (styleData.column) { + case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value; + case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value); + default: return styleData.value; + } + } + function formatSize(size) { + var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ]; + var suffixIndex = 0 + while ((size / 1024.0) > 1.1) { + size /= 1024.0; + ++suffixIndex; + } + + size = Math.round(size*1000)/1000; + size = size.toLocaleString() + + return size + " " + suffixes[suffixIndex]; + } } } + TableViewColumn { + id: fileNameColumn + role: "fileName" + title: "Name" + width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width + movable: false + resizable: true + } + TableViewColumn { + id: fileMofifiedColumn + role: "fileModified" + title: "Date" + width: 0.3 * fileTableView.width + movable: false + resizable: true + visible: !selectDirectory + } + TableViewColumn { + role: "fileSize" + title: "Size" + width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width + movable: false + resizable: true + visible: !selectDirectory + } + function navigateToRow(row) { currentRow = row; navigateToCurrentRow(); @@ -196,9 +540,9 @@ ModalWindow { function navigateToCurrentRow() { var row = fileTableView.currentRow var isFolder = model.isFolder(row); - var file = model.get(row, "fileURL"); + var file = model.get(row).filePath; if (isFolder) { - fileTableView.model.folder = file + fileTableView.model.folder = helper.pathToUrl(file); } else { okAction.trigger(); } @@ -213,7 +557,7 @@ ModalWindow { var newPrefix = prefix + event.text.toLowerCase(); var matchedIndex = -1; for (var i = 0; i < model.count; ++i) { - var name = model.get(i, "fileName").toLowerCase(); + var name = model.get(i).fileName.toLowerCase(); if (0 === name.indexOf(newPrefix)) { matchedIndex = i; break; @@ -254,14 +598,19 @@ ModalWindow { } break; } - } } TextField { id: currentSelection - style: TextFieldStyle { renderType: Text.QtRendering } - anchors { right: root.selectDirectory ? parent.right : selectionType.left; rightMargin: 8; left: parent.left; leftMargin: 8; top: selectionType.top } + label: selectDirectory ? "Directory:" : "File name:" + anchors { + left: parent.left + right: selectionType.visible ? selectionType.left: parent.right + rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0 + bottom: buttonRow.top + bottomMargin: hifi.dimensions.contentSpacing.y + } readOnly: !root.saveDialog activeFocusOnTab: !readOnly onActiveFocusChanged: if (activeFocus) { selectAll(); } @@ -270,27 +619,34 @@ ModalWindow { FileTypeSelection { id: selectionType - anchors { bottom: buttonRow.top; bottomMargin: 8; right: parent.right; rightMargin: 8; left: buttonRow.left } - visible: !selectDirectory + anchors { + top: currentSelection.top + left: buttonRow.left + right: parent.right + } + visible: !selectDirectory && filtersCount > 1 KeyNavigation.left: fileTableView KeyNavigation.right: openButton } Row { id: buttonRow - anchors.right: parent.right - anchors.rightMargin: 8 - anchors.bottom: parent.bottom - anchors.bottomMargin: 8 - spacing: 8 + anchors { + right: parent.right + bottom: parent.bottom + } + spacing: hifi.dimensions.contentSpacing.y + Button { id: openButton + color: hifi.buttons.blue action: okAction Keys.onReturnPressed: okAction.trigger() KeyNavigation.up: selectionType KeyNavigation.left: selectionType KeyNavigation.right: cancelButton } + Button { id: cancelButton action: cancelAction @@ -303,9 +659,16 @@ ModalWindow { Action { id: okAction - text: root.saveDialog ? "Save" : (root.selectDirectory ? "Choose" : "Open") - enabled: currentSelection.text ? true : false - onTriggered: okActionTimer.start(); + text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open" + enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false + onTriggered: { + if (!root.selectDirectory && !d.currentSelectionIsFolder + || root.selectDirectory && fileTableView.currentRow === -1) { + okActionTimer.start(); + } else { + fileTableView.navigateToCurrentRow(); + } + } } Timer { @@ -320,7 +683,6 @@ ModalWindow { return; } - // Handle the ambiguity between different cases // * typed name (with or without extension) // * full path vs relative vs filename only @@ -341,7 +703,6 @@ ModalWindow { if (!helper.urlIsWritable(selection)) { desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, - buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No, text: "Unable to write to location " + selection }) return; @@ -368,7 +729,7 @@ ModalWindow { Action { id: cancelAction text: "Cancel" - onTriggered: { canceled(); root.visible = false; } + onTriggered: { canceled(); root.shown = false; } } } @@ -385,5 +746,3 @@ ModalWindow { } } } - - diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index 30f492e36a..d390ea08bf 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import "../controls-uit" import "../styles-uit" -import "../windows-uit" +import "../windows" import "messageDialog" @@ -24,7 +24,7 @@ ModalWindow { implicitWidth: 640 implicitHeight: 320 destroyOnCloseButton: true - destroyOnInvisible: true + destroyOnHidden: true visible: true signal selected(int button); diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml index 40cc713397..5278118a22 100644 --- a/interface/resources/qml/dialogs/PreferencesDialog.qml +++ b/interface/resources/qml/dialogs/PreferencesDialog.qml @@ -13,14 +13,14 @@ import QtQuick.Controls 1.4 import "../controls-uit" as HifiControls import "../styles-uit" -import "../windows-uit" +import "../windows" import "preferences" -Window { +ScrollingWindow { id: root title: "Preferences" resizable: true - destroyOnInvisible: true + destroyOnHidden: true width: 500 height: 577 property var sections: [] diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index 0c7772dc94..05cb347169 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import "../controls-uit" import "../styles-uit" -import "../windows-uit" +import "../windows" ModalWindow { id: root diff --git a/interface/resources/qml/dialogs/fileDialog/FileTableView.qml b/interface/resources/qml/dialogs/fileDialog/FileTableView.qml deleted file mode 100644 index 93cdeebab0..0000000000 --- a/interface/resources/qml/dialogs/fileDialog/FileTableView.qml +++ /dev/null @@ -1,64 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.4 - -TableView { - id: root - onActiveFocusChanged: { - if (activeFocus && currentRow == -1) { - root.selection.select(0) - } - } - - itemDelegate: Component { - Item { - clip: true - Text { - x: 3 - anchors.verticalCenter: parent.verticalCenter - color: styleData.textColor - elide: styleData.elideMode - text: getText(); - font.italic: root.model.get(styleData.row, "fileIsDir") ? true : false - - function getText() { - switch (styleData.column) { - //case 1: return Date.fromLocaleString(locale, styleData.value, "yyyy-MM-dd hh:mm:ss"); - case 2: return root.model.get(styleData.row, "fileIsDir") ? "" : formatSize(styleData.value); - default: return styleData.value; - } - } - function formatSize(size) { - var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ]; - var suffixIndex = 0 - while ((size / 1024.0) > 1.1) { - size /= 1024.0; - ++suffixIndex; - } - - size = Math.round(size*1000)/1000; - size = size.toLocaleString() - - return size + " " + suffixes[suffixIndex]; - } - } - } - } - - TableViewColumn { - role: "fileName" - title: "Name" - width: 400 - } - TableViewColumn { - role: "fileModified" - title: "Date Modified" - width: 200 - } - TableViewColumn { - role: "fileSize" - title: "Size" - width: 200 - } -} - - diff --git a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml index 57ad2028ad..50a10974b5 100644 --- a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml +++ b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml @@ -1,11 +1,22 @@ +// +// FileTypeSelection.qml +// +// Created by Bradley Austin Davis on 29 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 -import "../../controls" as VrControls +import "../../controls-uit" -VrControls.ComboBox { +ComboBox { id: root property string filtersString: "All Files (*.*)"; property var currentFilter: [ "*.*" ]; + property int filtersCount: filtersString.split(';;').length // Per http://doc.qt.io/qt-5/qfiledialog.html#getOpenFileName the string can contain // multiple filters separated by semicolons diff --git a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml index e5bc9b80ef..16d25b3c4c 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtWebEngine 1.1 -import "../../windows-uit" as Windows +import "../../windows" as Windows import "../../controls-uit" as Controls import "../../styles-uit" @@ -23,15 +23,10 @@ Windows.Window { resizable: true modality: Qt.ApplicationModal - Item { - width: pane.contentWidth - implicitHeight: pane.scrollHeight - - Controls.WebView { - id: webview - anchors.fill: parent - url: "https://metaverse.highfidelity.com/marketplace?category=avatars" - focus: true - } + Controls.WebView { + id: webview + anchors.fill: parent + url: "https://metaverse.highfidelity.com/marketplace?category=avatars" + focus: true } } diff --git a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml index 9a19889938..2cf50891c9 100644 --- a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml @@ -65,7 +65,10 @@ Preference { verticalCenter: dataTextField.verticalCenter } onClicked: { - var browser = fileBrowserBuilder.createObject(desktop, { selectDirectory: true, folder: fileDialogHelper.pathToUrl(preference.value) }); + var browser = fileBrowserBuilder.createObject(desktop, { + selectDirectory: true, + dir: fileDialogHelper.pathToUrl(preference.value) + }); browser.selectedFile.connect(function(fileUrl){ console.log(fileUrl); dataTextField.text = fileDialogHelper.urlToPath(fileUrl); diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml new file mode 100644 index 0000000000..7758c5800a --- /dev/null +++ b/interface/resources/qml/hifi/Card.qml @@ -0,0 +1,96 @@ +// +// Card.qml +// qml/hifi +// +// Displays a clickable card representing a user story or destination. +// +// Created by Howard Stearns on 7/13/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 +import QtQuick 2.5 +import QtGraphicalEffects 1.0 +import "../styles-uit" + +Rectangle { + property var goFunction: null; + property var userStory: null; + property alias image: lobby; + property alias placeText: place.text; + property alias usersText: users.text; + property int textPadding: 20; + property int textSize: 24; + property string defaultPicture: "../../images/default-domain.gif"; + HifiConstants { id: hifi } + Image { + id: lobby; + width: parent.width; + height: parent.height; + source: defaultPicture; + fillMode: Image.PreserveAspectCrop; + // source gets filled in later + anchors.verticalCenter: parent.verticalCenter; + anchors.left: parent.left; + onStatusChanged: { + if (status == Image.Error) { + console.log("source: " + source + ": failed to load " + JSON.stringify(userStory)); + source = defaultPicture; + } + } + } + property int dropHorizontalOffset: 0; + property int dropVerticalOffset: 1; + property int dropRadius: 2; + property int dropSamples: 9; + property int dropSpread: 0; + DropShadow { + source: place; + anchors.fill: place; + horizontalOffset: dropHorizontalOffset; + verticalOffset: dropVerticalOffset; + radius: dropRadius; + samples: dropSamples; + color: hifi.colors.black; + spread: dropSpread; + } + DropShadow { + source: users; + anchors.fill: users; + horizontalOffset: dropHorizontalOffset; + verticalOffset: dropVerticalOffset; + radius: dropRadius; + samples: dropSamples; + color: hifi.colors.black; + spread: dropSpread; + } + RalewaySemiBold { + id: place; + color: hifi.colors.white; + size: textSize; + anchors { + top: parent.top; + left: parent.left; + margins: textPadding; + } + } + RalewayRegular { + id: users; + size: textSize; + color: hifi.colors.white; + anchors { + bottom: parent.bottom; + right: parent.right; + margins: textPadding; + } + } + MouseArea { + anchors.fill: parent; + acceptedButtons: Qt.LeftButton; + onClicked: goFunction(parent); + hoverEnabled: true; + } +} diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 59278a17b4..561bd722f2 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -1,14 +1,18 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtWebEngine 1.1; +import Qt.labs.settings 1.0 -import "../desktop" +import "../desktop" as OriginalDesktop import ".." +import "." +import "./toolbars" -Desktop { +OriginalDesktop.Desktop { id: desktop MouseArea { + id: hoverWatch anchors.fill: parent hoverEnabled: true propagateComposedEvents: true @@ -18,13 +22,6 @@ Desktop { acceptedButtons: Qt.NoButton } - Component.onCompleted: { - WebEngine.settings.javascriptCanOpenWindows = true; - WebEngine.settings.javascriptCanAccessClipboard = false; - WebEngine.settings.spatialNavigationEnabled = false; - WebEngine.settings.localContentCanAccessRemoteUrls = true; - } - // The tool window, one instance property alias toolWindow: toolWindow ToolWindow { id: toolWindow } @@ -47,7 +44,42 @@ Desktop { } } + property var toolbars: ({}) + Component { id: toolbarBuilder; Toolbar { } } + + Component.onCompleted: { + WebEngine.settings.javascriptCanOpenWindows = true; + WebEngine.settings.javascriptCanAccessClipboard = false; + WebEngine.settings.spatialNavigationEnabled = false; + WebEngine.settings.localContentCanAccessRemoteUrls = true; + + var sysToolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); + var toggleHudButton = sysToolbar.addButton({ + objectName: "hudToggle", + imageURL: "../../../icons/hud.svg", + visible: true, + pinned: true, + }); + + toggleHudButton.buttonState = Qt.binding(function(){ + return desktop.pinned ? 1 : 0 + }); + toggleHudButton.clicked.connect(function(){ + console.log("Clicked on hud button") + var overlayMenuItem = "Overlays" + MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)); + }); + } + + // Create or fetch a toolbar with the given name + function getToolbar(name) { + var result = toolbars[name]; + if (!result) { + result = toolbars[name] = toolbarBuilder.createObject(desktop, {}); + result.objectName = name; + } + return result; + } } - diff --git a/interface/resources/qml/hifi/MenuOption.qml b/interface/resources/qml/hifi/MenuOption.qml deleted file mode 100644 index 46cf5d9662..0000000000 --- a/interface/resources/qml/hifi/MenuOption.qml +++ /dev/null @@ -1,165 +0,0 @@ -import QtQuick 2.5 - -QtObject { - readonly property string aboutApp: "About Interface"; - readonly property string addRemoveFriends: "Add/Remove Friends..."; - readonly property string addressBar: "Show Address Bar"; - readonly property string animations: "Animations..."; - readonly property string animDebugDrawAnimPose: "Debug Draw Animation"; - readonly property string animDebugDrawDefaultPose: "Debug Draw Default Pose"; - readonly property string animDebugDrawPosition: "Debug Draw Position"; - readonly property string antialiasing: "Antialiasing"; - readonly property string assetMigration: "ATP Asset Migration"; - readonly property string assetServer: "Asset Server"; - readonly property string atmosphere: "Atmosphere"; - readonly property string attachments: "Attachments..."; - readonly property string audioNetworkStats: "Audio Network Stats"; - readonly property string audioNoiseReduction: "Audio Noise Reduction"; - readonly property string audioScope: "Show Scope"; - readonly property string audioScopeFiftyFrames: "Fifty"; - readonly property string audioScopeFiveFrames: "Five"; - readonly property string audioScopeFrames: "Display Frames"; - readonly property string audioScopePause: "Pause Scope"; - readonly property string audioScopeTwentyFrames: "Twenty"; - readonly property string audioStatsShowInjectedStreams: "Audio Stats Show Injected Streams"; - readonly property string audioTools: "Show Level Meter"; - readonly property string autoMuteAudio: "Auto Mute Microphone"; - readonly property string avatarReceiveStats: "Show Receive Stats"; - readonly property string back: "Back"; - readonly property string bandwidthDetails: "Bandwidth Details"; - readonly property string binaryEyelidControl: "Binary Eyelid Control"; - readonly property string bookmarkLocation: "Bookmark Location"; - readonly property string bookmarks: "Bookmarks"; - readonly property string cachesSize: "RAM Caches Size"; - readonly property string calibrateCamera: "Calibrate Camera"; - readonly property string cameraEntityMode: "Entity Mode"; - readonly property string centerPlayerInView: "Center Player In View"; - readonly property string chat: "Chat..."; - readonly property string collisions: "Collisions"; - readonly property string connexion: "Activate 3D Connexion Devices"; - readonly property string console_: "Console..."; - readonly property string controlWithSpeech: "Control With Speech"; - readonly property string copyAddress: "Copy Address to Clipboard"; - readonly property string copyPath: "Copy Path to Clipboard"; - readonly property string coupleEyelids: "Couple Eyelids"; - readonly property string crashInterface: "Crash Interface"; - readonly property string debugAmbientOcclusion: "Debug Ambient Occlusion"; - readonly property string decreaseAvatarSize: "Decrease Avatar Size"; - readonly property string deleteBookmark: "Delete Bookmark..."; - readonly property string disableActivityLogger: "Disable Activity Logger"; - readonly property string disableEyelidAdjustment: "Disable Eyelid Adjustment"; - readonly property string disableLightEntities: "Disable Light Entities"; - readonly property string disableNackPackets: "Disable Entity NACK Packets"; - readonly property string diskCacheEditor: "Disk Cache Editor"; - readonly property string displayCrashOptions: "Display Crash Options"; - readonly property string displayHandTargets: "Show Hand Targets"; - readonly property string displayModelBounds: "Display Model Bounds"; - readonly property string displayModelTriangles: "Display Model Triangles"; - readonly property string displayModelElementChildProxies: "Display Model Element Children"; - readonly property string displayModelElementProxy: "Display Model Element Bounds"; - readonly property string displayDebugTimingDetails: "Display Timing Details"; - readonly property string dontDoPrecisionPicking: "Don't Do Precision Picking"; - readonly property string dontRenderEntitiesAsScene: "Don't Render Entities as Scene"; - readonly property string echoLocalAudio: "Echo Local Audio"; - readonly property string echoServerAudio: "Echo Server Audio"; - readonly property string enable3DTVMode: "Enable 3DTV Mode"; - readonly property string enableCharacterController: "Enable avatar collisions"; - readonly property string expandMyAvatarSimulateTiming: "Expand /myAvatar/simulation"; - readonly property string expandMyAvatarTiming: "Expand /myAvatar"; - readonly property string expandOtherAvatarTiming: "Expand /otherAvatar"; - readonly property string expandPaintGLTiming: "Expand /paintGL"; - readonly property string expandUpdateTiming: "Expand /update"; - readonly property string faceshift: "Faceshift"; - readonly property string firstPerson: "First Person"; - readonly property string fivePointCalibration: "5 Point Calibration"; - readonly property string fixGaze: "Fix Gaze (no saccade)"; - readonly property string forward: "Forward"; - readonly property string frameTimer: "Show Timer"; - readonly property string fullscreenMirror: "Mirror"; - readonly property string help: "Help..."; - readonly property string increaseAvatarSize: "Increase Avatar Size"; - readonly property string independentMode: "Independent Mode"; - readonly property string inputMenu: "Avatar>Input Devices"; - readonly property string keyboardMotorControl: "Enable Keyboard Motor Control"; - readonly property string leapMotionOnHMD: "Leap Motion on HMD"; - readonly property string loadScript: "Open and Run Script File..."; - readonly property string loadScriptURL: "Open and Run Script from URL..."; - readonly property string lodTools: "LOD Tools"; - readonly property string login: "Login"; - readonly property string log: "Log"; - readonly property string logExtraTimings: "Log Extra Timing Details"; - readonly property string lowVelocityFilter: "Low Velocity Filter"; - readonly property string meshVisible: "Draw Mesh"; - readonly property string miniMirror: "Mini Mirror"; - readonly property string muteAudio: "Mute Microphone"; - readonly property string muteEnvironment: "Mute Environment"; - readonly property string muteFaceTracking: "Mute Face Tracking"; - readonly property string namesAboveHeads: "Names Above Heads"; - readonly property string noFaceTracking: "None"; - readonly property string octreeStats: "Entity Statistics"; - readonly property string onePointCalibration: "1 Point Calibration"; - readonly property string onlyDisplayTopTen: "Only Display Top Ten"; - readonly property string outputMenu: "Display"; - readonly property string packageModel: "Package Model..."; - readonly property string pair: "Pair"; - readonly property string physicsShowOwned: "Highlight Simulation Ownership"; - readonly property string physicsShowHulls: "Draw Collision Hulls"; - readonly property string pipelineWarnings: "Log Render Pipeline Warnings"; - readonly property string preferences: "General..."; - readonly property string quit: "Quit"; - readonly property string reloadAllScripts: "Reload All Scripts"; - readonly property string reloadContent: "Reload Content (Clears all caches)"; - readonly property string renderBoundingCollisionShapes: "Show Bounding Collision Shapes"; - readonly property string renderFocusIndicator: "Show Eye Focus"; - readonly property string renderLookAtTargets: "Show Look-at Targets"; - readonly property string renderLookAtVectors: "Show Look-at Vectors"; - readonly property string renderResolution: "Scale Resolution"; - readonly property string renderResolutionOne: "1"; - readonly property string renderResolutionTwoThird: "2/3"; - readonly property string renderResolutionHalf: "1/2"; - readonly property string renderResolutionThird: "1/3"; - readonly property string renderResolutionQuarter: "1/4"; - readonly property string renderAmbientLight: "Ambient Light"; - readonly property string renderAmbientLightGlobal: "Global"; - readonly property string renderAmbientLight0: "OLD_TOWN_SQUARE"; - readonly property string renderAmbientLight1: "GRACE_CATHEDRAL"; - readonly property string renderAmbientLight2: "EUCALYPTUS_GROVE"; - readonly property string renderAmbientLight3: "ST_PETERS_BASILICA"; - readonly property string renderAmbientLight4: "UFFIZI_GALLERY"; - readonly property string renderAmbientLight5: "GALILEOS_TOMB"; - readonly property string renderAmbientLight6: "VINE_STREET_KITCHEN"; - readonly property string renderAmbientLight7: "BREEZEWAY"; - readonly property string renderAmbientLight8: "CAMPUS_SUNSET"; - readonly property string renderAmbientLight9: "FUNSTON_BEACH_SUNSET"; - readonly property string resetAvatarSize: "Reset Avatar Size"; - readonly property string resetSensors: "Reset Sensors"; - readonly property string runningScripts: "Running Scripts..."; - readonly property string runTimingTests: "Run Timing Tests"; - readonly property string scriptEditor: "Script Editor..."; - readonly property string scriptedMotorControl: "Enable Scripted Motor Control"; - readonly property string showDSConnectTable: "Show Domain Connection Timing"; - readonly property string showBordersEntityNodes: "Show Entity Nodes"; - readonly property string showRealtimeEntityStats: "Show Realtime Entity Stats"; - readonly property string showWhosLookingAtMe: "Show Who's Looking at Me"; - readonly property string standingHMDSensorMode: "Standing HMD Sensor Mode"; - readonly property string simulateEyeTracking: "Simulate"; - readonly property string sMIEyeTracking: "SMI Eye Tracking"; - readonly property string stars: "Stars"; - readonly property string stats: "Stats"; - readonly property string stopAllScripts: "Stop All Scripts"; - readonly property string suppressShortTimings: "Suppress Timings Less than 10ms"; - readonly property string thirdPerson: "Third Person"; - readonly property string threePointCalibration: "3 Point Calibration"; - readonly property string throttleFPSIfNotFocus: "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp - readonly property string toolWindow: "Tool Window"; - readonly property string transmitterDrive: "Transmitter Drive"; - readonly property string turnWithHead: "Turn using Head"; - readonly property string useAudioForMouth: "Use Audio for Mouth"; - readonly property string useCamera: "Use Camera"; - readonly property string velocityFilter: "Velocity Filter"; - readonly property string visibleToEveryone: "Everyone"; - readonly property string visibleToFriends: "Friends"; - readonly property string visibleToNoOne: "No one"; - readonly property string worldAxes: "World Axes"; -} - diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml old mode 100755 new mode 100644 index 437e02e149..15467f8021 --- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml @@ -6,17 +6,17 @@ import QtQuick.Controls.Styles 1.4 import "../../styles-uit" import "../../controls-uit" as HifiControls -import "../../windows-uit" +import "../../windows" import "attachments" -Window { +ScrollingWindow { id: root title: "Attachments" objectName: "AttachmentsDialog" width: 600 height: 600 resizable: true - destroyOnInvisible: true + destroyOnHidden: true minSize: Qt.vector2d(400, 500) HifiConstants { id: hifi } diff --git a/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml index 86f195612c..45414cfaf8 100644 --- a/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml @@ -7,7 +7,7 @@ PreferencesDialog { id: root objectName: "AvatarPreferencesDialog" title: "Avatar Settings" - showCategories: [ "Avatar Basics", "Avatar Tuning", "Avatar Camera" ] + showCategories: [ "Avatar Basics", "Snapshots", "Avatar Tuning", "Avatar Camera" ] property var settings: Settings { category: root.objectName property alias x: root.x diff --git a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml index b2de108545..aeffb8e4bf 100644 --- a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml @@ -9,9 +9,9 @@ import "../models" import "../../styles-uit" import "../../controls-uit" as HifiControls -import "../../windows-uit" +import "../../windows" -Window { +ScrollingWindow { id: root resizable: true width: 600 diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index 071789fe16..3f05a140ae 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -15,17 +15,17 @@ import Qt.labs.settings 1.0 import "../../styles-uit" import "../../controls-uit" as HifiControls -import "../../windows-uit" +import "../../windows" -Window { +ScrollingWindow { id: root objectName: "RunningScripts" title: "Running Scripts" resizable: true - destroyOnInvisible: true - implicitWidth: 400 + destroyOnHidden: true + implicitWidth: 424 implicitHeight: isHMD ? 695 : 728 - minSize: Qt.vector2d(200, 300) + minSize: Qt.vector2d(424, 300) HifiConstants { id: hifi } @@ -34,6 +34,9 @@ Window { property var runningScriptsModel: ListModel { } property bool isHMD: false + onVisibleChanged: console.log("Running scripts visible changed to " + visible) + onShownChanged: console.log("Running scripts visible changed to " + visible) + Settings { category: "Overlay.RunningScripts" property alias x: root.x @@ -50,11 +53,6 @@ Window { updateRunningScripts(); } - function setDefaultFocus() { - // Work around FocusScope of scrollable window. - filterEdit.forceActiveFocus(); - } - function updateRunningScripts() { var runningScripts = ScriptDiscoveryService.getRunning(); runningScriptsModel.clear() @@ -83,6 +81,11 @@ Window { scripts.reloadAllScripts(); } + function loadDefaults() { + console.log("Load default scripts"); + scripts.loadOneScript(scripts.defaultScriptsPath + "/defaultScripts.js"); + } + function stopAll() { console.log("Stop all scripts"); scripts.stopAllScripts(); @@ -101,13 +104,13 @@ Window { spacing: hifi.dimensions.contentSpacing.x HifiControls.Button { - text: "Reload all" + text: "Reload All" color: hifi.buttons.black onClicked: reloadAll() } HifiControls.Button { - text: "Stop all" + text: "Remove All" color: hifi.buttons.red onClicked: stopAll() } @@ -118,11 +121,89 @@ Window { } HifiControls.Table { - tableModel: runningScriptsModel + model: runningScriptsModel + id: table height: 185 colorScheme: hifi.colorSchemes.dark anchors.left: parent.left anchors.right: parent.right + expandSelectedRow: true + + itemDelegate: Item { + anchors { + left: parent ? parent.left : undefined + leftMargin: hifi.dimensions.tablePadding + right: parent ? parent.right : undefined + rightMargin: hifi.dimensions.tablePadding + } + + FiraSansSemiBold { + id: textItem + text: styleData.value + size: hifi.fontSizes.tableText + color: table.colorScheme == hifi.colorSchemes.light + ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) + : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) + anchors { + left: parent.left + right: parent.right + top: parent.top + topMargin: 3 + } + + HiFiGlyphs { + id: reloadButton + text: hifi.glyphs.reloadSmall + color: reloadButtonArea.pressed ? hifi.colors.white : parent.color + anchors { + top: parent.top + right: stopButton.left + verticalCenter: parent.verticalCenter + } + MouseArea { + id: reloadButtonArea + anchors { fill: parent; margins: -2 } + onClicked: reloadScript(model.url) + } + } + + HiFiGlyphs { + id: stopButton + text: hifi.glyphs.closeSmall + color: stopButtonArea.pressed ? hifi.colors.white : parent.color + anchors { + top: parent.top + right: parent.right + verticalCenter: parent.verticalCenter + } + MouseArea { + id: stopButtonArea + anchors { fill: parent; margins: -2 } + onClicked: stopScript(model.url) + } + } + + } + + FiraSansSemiBold { + text: runningScriptsModel.get(styleData.row) ? runningScriptsModel.get(styleData.row).url : "" + elide: Text.ElideMiddle + size: hifi.fontSizes.tableText + color: table.colorScheme == hifi.colorSchemes.light + ? (styleData.selected ? hifi.colors.black : hifi.colors.lightGray) + : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) + anchors { + top: textItem.bottom + left: parent.left + right: parent.right + } + visible: styleData.selected + } + } + + TableViewColumn { + role: "name" + } } HifiControls.VerticalSpacer { @@ -137,7 +218,6 @@ Window { Row { spacing: hifi.dimensions.contentSpacing.x - anchors.right: parent.right HifiControls.Button { text: "from URL" @@ -175,6 +255,13 @@ Window { onTriggered: ApplicationInterface.loadDialog(); } } + + HifiControls.Button { + text: "Load Defaults" + color: hifi.buttons.black + height: 26 + onClicked: loadDefaults() + } } HifiControls.VerticalSpacer {} @@ -184,7 +271,6 @@ Window { isSearchField: true anchors.left: parent.left anchors.right: parent.right - focus: true colorScheme: hifi.colorSchemes.dark placeholderText: "Filter" onTextChanged: scriptsModel.filterRegExp = new RegExp("^.*" + text + ".*$", "i") diff --git a/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml b/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml deleted file mode 100644 index f99b770a78..0000000000 --- a/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml +++ /dev/null @@ -1,117 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.XmlListModel 2.0 - -import "../../windows" -import "../../js/Utils.js" as Utils -import "../models" - -Window { - id: root - resizable: true - width: 516 - height: 616 - minSize: Qt.vector2d(500, 600); - maxSize: Qt.vector2d(1000, 800); - - property alias source: image.source - - Rectangle { - anchors.fill: parent - color: "white" - - Item { - anchors { fill: parent; margins: 8 } - - Image { - id: image - anchors { top: parent.top; left: parent.left; right: parent.right; bottom: notesLabel.top; bottomMargin: 8 } - fillMode: Image.PreserveAspectFit - } - - Text { - id: notesLabel - anchors { left: parent.left; bottom: notes.top; bottomMargin: 8; } - text: "Notes about this image" - font.pointSize: 14 - font.bold: true - color: "#666" - } - - TextArea { - id: notes - anchors { left: parent.left; bottom: parent.bottom; right: shareButton.left; rightMargin: 8 } - height: 60 - } - - Button { - id: shareButton - anchors { verticalCenter: notes.verticalCenter; right: parent.right; } - width: 120; height: 50 - text: "Share" - - style: ButtonStyle { - background: Rectangle { - implicitWidth: 120 - implicitHeight: 50 - border.width: control.activeFocus ? 2 : 1 - color: "#333" - radius: 9 - } - label: Text { - color: shareButton.enabled ? "white" : "gray" - font.pixelSize: 18 - font.bold: true - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - anchors.fill: parent - text: shareButton.text - } - } - - onClicked: { - enabled = false; - uploadTimer.start(); - } - - Timer { - id: uploadTimer - running: false - interval: 5 - repeat: false - onTriggered: { - var uploaded = SnapshotUploader.uploadSnapshot(root.source.toString()) - console.log("Uploaded result " + uploaded) - if (!uploaded) { - console.log("Upload failed "); - } - } - } - } - } - - Action { - id: shareAction - text: qsTr("OK") - enabled: root.result ? true : false - shortcut: Qt.Key_Return - onTriggered: { - root.destroy(); - } - } - - Action { - id: cancelAction - text: qsTr("Cancel") - shortcut: Qt.Key_Escape - onTriggered: { - root.destroy(); - } - } - } -} - - - - diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml old mode 100755 new mode 100644 index 1277c459ce..04e3934535 --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -8,7 +8,7 @@ import "." import ".." import "../../../styles-uit" import "../../../controls-uit" as HifiControls -import "../../../windows-uit" +import "../../../windows" Item { height: column.height + 2 * 8 diff --git a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml index e1d7b6d4a3..3d109cc2a5 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml @@ -3,7 +3,7 @@ import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControls -import "../../../windows-uit" +import "../../../windows" Item { id: root diff --git a/interface/resources/qml/hifi/overlays/ImageOverlay.qml b/interface/resources/qml/hifi/overlays/ImageOverlay.qml index b10b66f07c..b509f0ce3a 100644 --- a/interface/resources/qml/hifi/overlays/ImageOverlay.qml +++ b/interface/resources/qml/hifi/overlays/ImageOverlay.qml @@ -1,5 +1,6 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 +import QtGraphicalEffects 1.0 import "." @@ -44,6 +45,12 @@ Overlay { } } + ColorOverlay { + id: color + anchors.fill: image + source: image + } + function updateSubImage(subImage) { var keys = Object.keys(subImage); for (var i = 0; i < keys.length; ++i) { @@ -70,6 +77,7 @@ Overlay { case "alpha": root.opacity = value; break; case "imageURL": image.source = value; break; case "subImage": updateSubImage(value); break; + case "color": color.color = Qt.rgba(value.red / 255, value.green / 255, value.blue / 255, root.opacity); break; default: console.log("OVERLAY Unhandled image property " + key); } } diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml new file mode 100644 index 0000000000..30989be688 --- /dev/null +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -0,0 +1,150 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import Qt.labs.settings 1.0 + +import "../../windows" +import "." + +Window { + id: window + frame: ToolFrame { + horizontalSpacers: horizontal + verticalSpacers: !horizontal + } + hideBackground: true + resizable: false + destroyOnCloseButton: false + destroyOnHidden: false + closable: false + shown: true + width: content.width + height: content.height + // Disable this window from being able to call 'desktop.raise() and desktop.showDesktop' + activator: Item {} + property bool horizontal: true + property real buttonSize: 50; + property var buttons: [] + property var container: horizontal ? row : column + + Settings { + category: "toolbar/" + window.objectName + property alias x: window.x + property alias y: window.y + } + + onHorizontalChanged: { + var newParent = horizontal ? row : column; + for (var i in buttons) { + var child = buttons[i]; + child.parent = newParent; + if (horizontal) { + child.y = 0 + } else { + child.x = 0 + } + } + } + + Item { + id: content + implicitHeight: horizontal ? row.height : column.height + implicitWidth: horizontal ? row.width : column.width + + Row { + id: row + spacing: 6 + } + + Column { + id: column + spacing: 6 + } + + Component { id: toolbarButtonBuilder; ToolbarButton { } } + + Connections { + target: desktop + onPinnedChanged: { + if (!window.pinned) { + return; + } + var newPinned = desktop.pinned; + for (var i in buttons) { + var child = buttons[i]; + if (desktop.pinned) { + if (!child.pinned) { + child.visible = false; + } + } else { + child.visible = true; + } + } + } + } + } + + + function findButtonIndex(name) { + if (!name) { + return -1; + } + + for (var i in buttons) { + var child = buttons[i]; + if (child.objectName === name) { + return i; + } + } + return -1; + } + + function findButton(name) { + var index = findButtonIndex(name); + if (index < 0) { + return; + } + return buttons[index]; + } + + function addButton(properties) { + properties = properties || {} + + // If a name is specified, then check if there's an existing button with that name + // and return it if so. This will allow multiple clients to listen to a single button, + // and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded + var result = findButton(properties.objectName); + if (result) { + return result; + } + properties.toolbar = this; + properties.opacity = 0; + result = toolbarButtonBuilder.createObject(container, properties); + buttons.push(result); + result.opacity = 1; + updatePinned(); + return result; + } + + function removeButton(name) { + var index = findButtonIndex(name); + if (index < -1) { + console.warn("Tried to remove non-existent button " + name); + return; + } + buttons[index].destroy(); + buttons.splice(index, 1); + updatePinned(); + } + + function updatePinned() { + var newPinned = false; + for (var i in buttons) { + var child = buttons[i]; + if (child.pinned) { + newPinned = true; + break; + } + } + pinned = newPinned; + } +} diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml new file mode 100644 index 0000000000..aed90cd433 --- /dev/null +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -0,0 +1,64 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Item { + id: button + property alias imageURL: image.source + property alias alpha: image.opacity + property var subImage; + property int yOffset: 0 + property int buttonState: 0 + property int hoverState: -1 + property int defaultState: -1 + property var toolbar; + property real size: 50 // toolbar ? toolbar.buttonSize : 50 + width: size; height: size + property bool pinned: false + clip: true + + onButtonStateChanged: { + yOffset = size * buttonState; + } + + Component.onCompleted: { + if (subImage) { + if (subImage.y) { + yOffset = subImage.y; + } + } + } + + signal clicked() + + Image { + id: image + y: -button.yOffset; + width: parent.width + } + + Timer { + id: asyncClickSender + interval: 10 + repeat: false + running: false + onTriggered: button.clicked(); + } + + MouseArea { + id: mouseArea + hoverEnabled: true + anchors.fill: parent + onClicked: asyncClickSender.start(); + onEntered: { + if (hoverState >= 0) { + buttonState = hoverState; + } + } + onExited: { + if (defaultState >= 0) { + buttonState = defaultState; + } + } + } +} + diff --git a/interface/resources/qml/menus/MenuMouseHandler.qml b/interface/resources/qml/menus/MenuMouseHandler.qml index 9ba158cb28..48574d41e5 100644 --- a/interface/resources/qml/menus/MenuMouseHandler.qml +++ b/interface/resources/qml/menus/MenuMouseHandler.qml @@ -39,6 +39,19 @@ Item { onSelected: d.handleSelection(subMenu, currentItem, item) } } + property var delay: Timer { // No setTimeout in QML. + property var menuItem: null; + interval: 0 + repeat: false + running: false + function trigger(item) { // Capture item and schedule asynchronous Timer. + menuItem = item; + start(); + } + onTriggered: { + menuItem.trigger(); // Now trigger the item. + } + } function toModel(items) { var result = modelMaker.createObject(desktop); @@ -128,7 +141,8 @@ Item { case MenuItemType.Item: console.log("Triggering " + item.text) - item.trigger(); + // Don't block waiting for modal dialogs and such that the menu might open. + delay.trigger(item); clearMenus(); break; } diff --git a/interface/resources/qml/menus/VrMenuView.qml b/interface/resources/qml/menus/VrMenuView.qml index d8cc0e6667..5db13fc160 100644 --- a/interface/resources/qml/menus/VrMenuView.qml +++ b/interface/resources/qml/menus/VrMenuView.qml @@ -45,6 +45,7 @@ FocusScope { onVisibleChanged: recalcSize(); onCountChanged: recalcSize(); focus: true + highlightMoveDuration: 0 highlight: Rectangle { anchors { diff --git a/interface/resources/qml/styles-uit/FiraSansRegular.qml b/interface/resources/qml/styles-uit/FiraSansRegular.qml new file mode 100644 index 0000000000..4ae0826772 --- /dev/null +++ b/interface/resources/qml/styles-uit/FiraSansRegular.qml @@ -0,0 +1,23 @@ +// +// FiraSansRegular.qml +// +// Created by David Rowe on 12 May 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +Text { + id: root + FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; } + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: firaSansRegular.name +} diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index cb4d2157ca..f2698da574 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -89,12 +89,16 @@ Item { readonly property color transparent: "#00ffffff" // Control specific colors - readonly property color tableRowLightOdd: white50 - readonly property color tableRowLightEven: "#1a575757" - readonly property color tableRowDarkOdd: "#80393939" - readonly property color tableRowDarkEven: "#a6181818" - readonly property color tableScrollHandle: "#707070" - readonly property color tableScrollBackground: "#323232" + readonly property color tableRowLightOdd: "#eaeaea" // Equivalent to white50 over #e3e3e3 background + readonly property color tableRowLightEven: "#c6c6c6" // Equivavlent to "#1a575757" over #e3e3e3 background + readonly property color tableRowDarkOdd: "#2e2e2e" // Equivalent to "#80393939" over #404040 background + readonly property color tableRowDarkEven: "#1c1c1c" // Equivalent to "#a6181818" over #404040 background + readonly property color tableBackgroundLight: tableRowLightEven + readonly property color tableBackgroundDark: tableRowDarkEven + readonly property color tableScrollHandleLight: tableRowLightOdd + readonly property color tableScrollHandleDark: "#707070" + readonly property color tableScrollBackgroundLight: tableRowLightEven + readonly property color tableScrollBackgroundDark: "#323232" readonly property color checkboxLightStart: "#ffffff" readonly property color checkboxLightFinish: "#afafaf" readonly property color checkboxDarkStart: "#7d7d7d" @@ -137,10 +141,11 @@ Item { readonly property real textPadding: 8 readonly property real sliderHandleSize: 18 readonly property real sliderGrooveHeight: 8 - readonly property real spinnerSize: 42 + readonly property real frameIconSize: 22 + readonly property real spinnerSize: 50 readonly property real tablePadding: 12 readonly property real tableRowHeight: largeScreen ? 26 : 23 - readonly property real tableHeaderHeight: 40 + readonly property real tableHeaderHeight: 29 readonly property vector2d modalDialogMargin: Qt.vector2d(50, 30) readonly property real modalDialogTitleHeight: 40 readonly property real controlLineHeight: 28 // Height of spinbox control on 1920 x 1080 monitor @@ -157,6 +162,8 @@ Item { readonly property real textFieldInput: dimensions.largeScreen ? 15 : 12 readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9 readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24 + readonly property real tableHeading: dimensions.largeScreen ? 12 : 10 + readonly property real tableHeadingIcon: dimensions.largeScreen ? 40 : 33 readonly property real tableText: dimensions.largeScreen ? 15 : 12 readonly property real buttonLabel: dimensions.largeScreen ? 13 : 9 readonly property real iconButton: dimensions.largeScreen ? 13 : 9 diff --git a/interface/resources/qml/windows-uit/DefaultFrame.qml b/interface/resources/qml/windows-uit/DefaultFrame.qml deleted file mode 100644 index 04905656ce..0000000000 --- a/interface/resources/qml/windows-uit/DefaultFrame.qml +++ /dev/null @@ -1,111 +0,0 @@ -// -// DefaultFrame.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtGraphicalEffects 1.0 - -import "." -import "../styles-uit" - -Frame { - HifiConstants { id: hifi } - - Rectangle { - // Dialog frame - id: frameContent - anchors { - topMargin: -frameMarginTop - leftMargin: -frameMarginLeft - rightMargin: -frameMarginRight - bottomMargin: -frameMarginBottom - } - anchors.fill: parent - color: hifi.colors.baseGrayHighlight40 - border { - width: hifi.dimensions.borderWidth - color: hifi.colors.faintGray50 - } - radius: hifi.dimensions.borderRadius - - // Allow dragging of the window - MouseArea { - anchors.fill: parent - drag.target: window - } - - Row { - id: controlsRow - anchors { - right: parent.right; - top: parent.top; - topMargin: frameMargin + 1 // Move down a little to visually align with the title - rightMargin: frameMarginRight; - } - spacing: iconSize / 4 - - HiFiGlyphs { - // "Pin" button - visible: false - text: (frame.pinned && !pinClickArea.containsMouse) || (!frame.pinned && pinClickArea.containsMouse) ? hifi.glyphs.pinInverted : hifi.glyphs.pin - color: pinClickArea.containsMouse && !pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white - size: iconSize - MouseArea { - id: pinClickArea - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - onClicked: { frame.pin(); mouse.accepted = false; } - } - } - - HiFiGlyphs { - // "Close" button - visible: window ? window.closable : false - text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close - color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white - size: iconSize - MouseArea { - id: closeClickArea - anchors.fill: parent - hoverEnabled: true - onClicked: window.visible = false; - } - } - } - - RalewayRegular { - // Title - id: titleText - anchors { - left: parent.left - leftMargin: frameMarginLeft + hifi.dimensions.contentMargin.x - right: controlsRow.left - rightMargin: iconSize - top: parent.top - topMargin: frameMargin - } - text: window ? window.title : "" - color: hifi.colors.white - size: hifi.fontSizes.overlayTitle - } - - DropShadow { - source: titleText - anchors.fill: titleText - horizontalOffset: 2 - verticalOffset: 2 - samples: 2 - color: hifi.colors.baseGrayShadow60 - visible: (window && window.focus) - cached: true - } - } -} - diff --git a/interface/resources/qml/windows-uit/Fadable.qml b/interface/resources/qml/windows-uit/Fadable.qml deleted file mode 100644 index 34990c2147..0000000000 --- a/interface/resources/qml/windows-uit/Fadable.qml +++ /dev/null @@ -1,60 +0,0 @@ -// -// Fadable.qml -// -// Created by Bradley Austin Davis on 15 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtGraphicalEffects 1.0 - -import "../styles-uit" - -// Enable window visibility transitions -FocusScope { - id: root - HifiConstants { id: hifi } - - Component.onCompleted: { - fadeTargetProperty = visible ? 1.0 : 0.0 - } - - // The target property to animate, usually scale or opacity - property alias fadeTargetProperty: root.opacity - // always start the property at 0 to enable fade in on creation - fadeTargetProperty: 0 - // DO NOT set visible to false or when derived types override it it - // will short circuit the fade in on initial visibility - // visible: false <--- NO - - // Some dialogs should be destroyed when they become - // invisible, so handle that - onVisibleChanged: { - // If someone directly set the visibility to false - // toggle it back on and use the targetVisible flag to transition - // via fading. - if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) { - var target = visible; - visible = !visible; - fadeTargetProperty = target ? 1.0 : 0.0; - return; - } - } - - // The actual animator - Behavior on fadeTargetProperty { - NumberAnimation { - duration: hifi.effects.fadeInDuration - easing.type: Easing.InOutCubic - } - } - - // Once we're transparent, disable the dialog's visibility - onFadeTargetPropertyChanged: { - visible = (fadeTargetProperty != 0.0); - } -} diff --git a/interface/resources/qml/windows-uit/Frame.qml b/interface/resources/qml/windows-uit/Frame.qml deleted file mode 100644 index f21097ea62..0000000000 --- a/interface/resources/qml/windows-uit/Frame.qml +++ /dev/null @@ -1,133 +0,0 @@ -// -// Frame.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtGraphicalEffects 1.0 - -import "../styles-uit" -import "../js/Utils.js" as Utils - -Item { - id: frame - HifiConstants { id: hifi } - - default property var decoration - - property bool gradientsSupported: desktop.gradientsSupported - - readonly property int iconSize: 22 - readonly property int frameMargin: 9 - readonly property int frameMarginLeft: frameMargin - readonly property int frameMarginRight: frameMargin - readonly property int frameMarginTop: 2 * frameMargin + iconSize - readonly property int frameMarginBottom: iconSize + 11 - - // Frames always fill their parents, but their decorations may extend - // beyond the window via negative margin sizes - anchors.fill: parent - - children: [ - focusShadow, - decoration, - sizeOutline, - debugZ, - sizeDrag - ] - - Text { - id: debugZ - visible: DebugQML - text: window ? "Z: " + window.z : "" - y: window ? window.height + 4 : 0 - } - - function deltaSize(dx, dy) { - var newSize = Qt.vector2d(window.width + dx, window.height + dy); - newSize = Utils.clampVector(newSize, window.minSize, window.maxSize); - window.width = newSize.x - window.height = newSize.y - } - - RadialGradient { - id: focusShadow - width: 1.66 * window.width - height: 1.66 * window.height - x: (window.width - width) / 2 - y: window.height / 2 - 0.375 * height - visible: gradientsSupported && window && window.focus && pane.visible - gradient: Gradient { - // GradientStop position 0.5 is at full circumference of circle that fits inside the square. - GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity - GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity - GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity - GradientStop { position: 1.0; color: "#00000000" } - } - cached: true - } - - Rectangle { - id: sizeOutline - x: -frameMarginLeft - y: -frameMarginTop - width: window ? window.width + frameMarginLeft + frameMarginRight : 0 - height: window ? window.height + frameMarginTop + frameMarginBottom : 0 - color: hifi.colors.baseGrayHighlight15 - border.width: 3 - border.color: hifi.colors.white50 - radius: hifi.dimensions.borderRadius - visible: window ? !pane.visible : false - } - - MouseArea { - // Resize handle - id: sizeDrag - width: iconSize - height: iconSize - enabled: window ? window.resizable : false - hoverEnabled: true - x: window ? window.width + frameMarginRight - iconSize : 0 - y: window ? window.height + 4 : 0 - property vector2d pressOrigin - property vector2d sizeOrigin - property bool hid: false - onPressed: { - //console.log("Pressed on size") - pressOrigin = Qt.vector2d(mouseX, mouseY) - sizeOrigin = Qt.vector2d(window.content.width, window.content.height) - hid = false; - } - onReleased: { - if (hid) { - pane.visible = true - frameContent.visible = true - hid = false; - } - } - onPositionChanged: { - if (pressed) { - if (pane.visible) { - pane.visible = false; - frameContent.visible = false - hid = true; - } - var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin); - frame.deltaSize(delta.x, delta.y) - } - } - HiFiGlyphs { - visible: sizeDrag.enabled - x: -11 // Move a little to visually align - y: -4 // "" - text: hifi.glyphs.resizeHandle - size: iconSize + 10 - color: sizeDrag.containsMouse || sizeDrag.pressed ? hifi.colors.white : hifi.colors.white50 - } - } -} diff --git a/interface/resources/qml/windows-uit/ModalFrame.qml b/interface/resources/qml/windows-uit/ModalFrame.qml deleted file mode 100644 index 77344829d5..0000000000 --- a/interface/resources/qml/windows-uit/ModalFrame.qml +++ /dev/null @@ -1,83 +0,0 @@ -// -// ModalFrame.qml -// -// Created by Bradley Austin Davis on 15 Jan 2016 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 - -import "." -import "../controls-uit" -import "../styles-uit" - -Frame { - HifiConstants { id: hifi } - - Rectangle { - id: modalFrame - - readonly property bool hasTitle: window.title != "" - - anchors { - fill: parent - topMargin: -hifi.dimensions.modalDialogMargin.y - (modalFrame.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) - leftMargin: -hifi.dimensions.modalDialogMargin.x - rightMargin: -hifi.dimensions.modalDialogMargin.x - bottomMargin: -hifi.dimensions.modalDialogMargin.y - } - - border { - width: hifi.dimensions.borderWidth - color: hifi.colors.lightGrayText80 - } - radius: hifi.dimensions.borderRadius - color: hifi.colors.faintGray - - Item { - visible: modalFrame.hasTitle - anchors.fill: parent - anchors { - topMargin: -parent.anchors.topMargin - leftMargin: -parent.anchors.leftMargin - rightMargin: -parent.anchors.rightMargin - } - - Item { - width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0) - x: (parent.width - width) / 2 - - onWidthChanged: window.titleWidth = width - - HiFiGlyphs { - id: icon - text: window.iconText ? window.iconText : "" - size: window.iconSize ? window.iconSize : 30 - color: hifi.colors.lightGray - visible: text != "" - anchors.verticalCenter: title.verticalCenter - anchors.left: parent.left - } - RalewayRegular { - id: title - text: window.title - elide: Text.ElideRight - color: hifi.colors.baseGrayHighlight - size: hifi.fontSizes.overlayTitle - y: -hifi.dimensions.modalDialogTitleHeight - anchors.right: parent.right - } - } - - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - height: 1 - color: hifi.colors.lightGray - } - } - } -} diff --git a/interface/resources/qml/windows-uit/ModalWindow.qml b/interface/resources/qml/windows-uit/ModalWindow.qml deleted file mode 100644 index af099eb275..0000000000 --- a/interface/resources/qml/windows-uit/ModalWindow.qml +++ /dev/null @@ -1,22 +0,0 @@ -// -// ModalWindow.qml -// -// Created by Bradley Austin Davis on 22 Jan 2016 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 - -import "." - -Window { - id: window - anchors.centerIn: parent - modality: Qt.ApplicationModal - destroyOnCloseButton: true - destroyOnInvisible: true - frame: ModalFrame{} -} diff --git a/interface/resources/qml/windows-uit/Window.qml b/interface/resources/qml/windows-uit/Window.qml deleted file mode 100644 index e9477f3c7e..0000000000 --- a/interface/resources/qml/windows-uit/Window.qml +++ /dev/null @@ -1,342 +0,0 @@ -// -// Window.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtGraphicalEffects 1.0 - -import "." -import "../styles-uit" - -// FIXME how do I set the initial position of a window without -// overriding places where the a individual client of the window -// might be setting the position with a Settings{} element? - -// FIXME how to I enable dragging without allowing the window to lay outside -// of the desktop? How do I ensure when the desktop resizes all the windows -// are still at least partially visible? -Fadable { - id: window - HifiConstants { id: hifi } - - // The Window size is the size of the content, while the frame - // decorations can extend outside it. - implicitHeight: content ? content.height : 0 - implicitWidth: content ? content.width : 0 - x: desktop.invalid_position; y: desktop.invalid_position; - enabled: visible - - signal windowDestroyed(); - - property int modality: Qt.NonModal - readonly property bool topLevelWindow: true - property string title - // Should the window be closable control? - property bool closable: true - // Should the window try to remain on top of other windows? - property bool alwaysOnTop: false - // Should hitting the close button hide or destroy the window? - property bool destroyOnCloseButton: true - // Should hiding the window destroy it or just hide it? - property bool destroyOnInvisible: false - // FIXME support for pinned / unpinned pending full design - // property bool pinnable: false - // property bool pinned: false - property bool resizable: false - property bool gradientsSupported: desktop.gradientsSupported - - property vector2d minSize: Qt.vector2d(100, 100) - property vector2d maxSize: Qt.vector2d(1280, 800) - - // The content to place inside the window, determined by the client - default property var content - - property var footer: Item { } // Optional static footer at the bottom of the dialog. - - function setDefaultFocus() {} // Default function; can be overridden by dialogs. - - property var rectifier: Timer { - property bool executing: false; - interval: 100 - repeat: false - running: false - - onTriggered: { - executing = true; - x = Math.floor(x); - y = Math.floor(y); - executing = false; - } - - function begin() { - if (!executing) { - restart(); - } - } - } - onXChanged: rectifier.begin(); - onYChanged: rectifier.begin(); - - // This mouse area serves to raise the window. To function, it must live - // in the window and have a higher Z-order than the content, but follow - // the position and size of frame decoration - property var activator: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.leftMargin - y: frame.decoration.anchors.topMargin - propagateComposedEvents: true - acceptedButtons: Qt.AllButtons - enabled: window.visible - onPressed: { - //console.log("Pressed on activator area"); - window.raise(); - mouse.accepted = false; - } - } - - // This mouse area serves to swallow mouse events while the mouse is over the window - // to prevent things like mouse wheel events from reaching the application and changing - // the camera if the user is scrolling through a list and gets to the end. - property var swallower: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.leftMargin - y: frame.decoration.anchors.topMargin - hoverEnabled: true - acceptedButtons: Qt.AllButtons - enabled: window.visible - onClicked: {} - onDoubleClicked: {} - onPressAndHold: {} - onReleased: {} - onWheel: {} - } - - // Default to a standard frame. Can be overriden to provide custom - // frame styles, like a full desktop frame to simulate a modal window - property var frame: DefaultFrame { } - - // Scrollable window content. - property var pane: Item { - property bool isScrolling: scrollView.height < scrollView.contentItem.height - property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) - property int scrollHeight: scrollView.height - - anchors.fill: parent - anchors.rightMargin: isScrolling ? 11 : 0 - - Rectangle { - id: contentBackground - anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 11 : 0 - color: hifi.colors.baseGray - visible: modality != Qt.ApplicationModal - } - - LinearGradient { - visible: gradientsSupported && modality != Qt.ApplicationModal - anchors.top: contentBackground.bottom - anchors.left: contentBackground.left - width: contentBackground.width - 1 - height: 4 - start: Qt.point(0, 0) - end: Qt.point(0, 4) - gradient: Gradient { - GradientStop { position: 0.0; color: hifi.colors.darkGray } - GradientStop { position: 1.0; color: hifi.colors.darkGray0 } - } - cached: true - } - - ScrollView { - id: scrollView - contentItem: content - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAsNeeded - anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 1 : 0 - anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0 - - style: ScrollViewStyle { - - padding.right: -7 // Move to right away from content. - - handle: Item { - implicitWidth: 8 - Rectangle { - radius: 4 - color: hifi.colors.white30 - anchors { - fill: parent - leftMargin: 2 // Finesse size and position. - topMargin: 1 - bottomMargin: 1 - } - } - } - - scrollBarBackground: Item { - implicitWidth: 10 - Rectangle { - color: hifi.colors.darkGray30 - radius: 4 - anchors { - fill: parent - topMargin: -1 // Finesse size - bottomMargin: -2 - } - } - } - - incrementControl: Item { - visible: false - } - - decrementControl: Item { - visible: false - } - } - } - - Rectangle { - // Optional non-scrolling footer. - id: footerPane - anchors { - left: parent.left - bottom: parent.bottom - } - width: parent.contentWidth - height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3 - color: hifi.colors.baseGray - visible: footer.height > 0 - - Item { - // Horizontal rule. - anchors.fill: parent - - Rectangle { - width: parent.width - height: 1 - y: 1 // Stop displaying content just above horizontal rule/=. - color: hifi.colors.baseGrayShadow - } - - Rectangle { - width: parent.width - height: 1 - y: 2 - color: hifi.colors.baseGrayHighlight - } - } - - Item { - anchors.fill: parent - anchors.topMargin: 3 // Horizontal rule. - children: [ footer ] - } - } - } - - children: [ swallower, frame, pane, activator ] - - Component.onCompleted: { - window.parentChanged.connect(raise); - raise(); - setDefaultFocus(); - centerOrReposition(); - } - Component.onDestruction: { - window.parentChanged.disconnect(raise); // Prevent warning on shutdown - windowDestroyed(); - } - - onVisibleChanged: { - if (!visible && destroyOnInvisible) { - destroy(); - return; - } - if (visible) { - raise(); - } - enabled = visible - - if (visible && parent) { - centerOrReposition(); - } - } - - function centerOrReposition() { - if (x == desktop.invalid_position && y == desktop.invalid_position) { - desktop.centerOnVisible(window); - } else { - desktop.repositionOnVisible(window); - } - } - - function raise() { - if (visible && parent) { - desktop.raise(window) - } - } - - function pin() { -// pinned = ! pinned - } - - // our close function performs the same way as the OffscreenUI class: - // don't do anything but manipulate the targetVisible flag and let the other - // mechanisms decide if the window should be destroyed after the close - // animation completes - // FIXME using this close function messes up the visibility signals received by the - // type and it's derived types -// function close() { -// console.log("Closing " + window) -// if (destroyOnCloseButton) { -// destroyOnInvisible = true -// } -// visible = false; -// } - - function framedRect() { - if (!frame || !frame.decoration) { - return Qt.rect(0, 0, window.width, window.height) - } - return Qt.rect(frame.decoration.anchors.leftMargin, frame.decoration.anchors.topMargin, - window.width - frame.decoration.anchors.leftMargin - frame.decoration.anchors.rightMargin, - window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin) - } - - Keys.onPressed: { - switch(event.key) { - case Qt.Key_Control: - case Qt.Key_Shift: - case Qt.Key_Meta: - case Qt.Key_Alt: - break; - - case Qt.Key_W: - if (window.closable && (event.modifiers === Qt.ControlModifier)) { - visible = false - event.accepted = true - } - // fall through - - default: - // Consume unmodified keyboard entries while the window is focused, to prevent them - // from propagating to the application - if (event.modifiers === Qt.NoModifier) { - event.accepted = true; - } - break; - } - } -} diff --git a/interface/resources/qml/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml new file mode 100644 index 0000000000..843ae25596 --- /dev/null +++ b/interface/resources/qml/windows/Decoration.qml @@ -0,0 +1,93 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + +Rectangle { + HifiConstants { id: hifi } + + signal inflateDecorations(); + signal deflateDecorations(); + + property int frameMargin: 9 + property int frameMarginLeft: frameMargin + property int frameMarginRight: frameMargin + property int frameMarginTop: 2 * frameMargin + iconSize + property int frameMarginBottom: iconSize + 11 + + anchors { + topMargin: -frameMarginTop + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + bottomMargin: -frameMarginBottom + } + anchors.fill: parent + color: hifi.colors.baseGrayHighlight40 + border { + width: hifi.dimensions.borderWidth + color: hifi.colors.faintGray50 + } + radius: hifi.dimensions.borderRadius + + // Enable dragging of the window, + // detect mouseover of the window (including decoration) + MouseArea { + id: decorationMouseArea + anchors.fill: parent + drag.target: window + hoverEnabled: true + onEntered: window.mouseEntered(); + onExited: { + if (!containsMouseGlobal()) { + window.mouseExited(); + } + } + + function containsMouseGlobal() { + var reticlePos = Reticle.position; + var globalPosition = decorationMouseArea.mapToItem(desktop, 0, 0); + var localPosition = { + x: reticlePos.x - globalPosition.x, + y: reticlePos.y - globalPosition.y, + }; + return localPosition.x >= 0 && localPosition.x <= width && + localPosition.y >= 0 && localPosition.y <= height; + } + + } + Connections { + target: window + onMouseEntered: { + if (desktop.hmdHandMouseActive) { + root.inflateDecorations() + } + } + onMouseExited: { + root.deflateDecorations(); + } + } + Connections { + target: desktop + onHmdHandMouseActiveChanged: { + if (desktop.hmdHandMouseActive) { + if (decorationMouseArea.containsMouse) { + root.inflateDecorations(); + } + } else { + root.deflateDecorations(); + } + } + } +} + diff --git a/interface/resources/qml/windows/DefaultFrame.qml b/interface/resources/qml/windows/DefaultFrame.qml index c58f9ca545..33c2818849 100644 --- a/interface/resources/qml/windows/DefaultFrame.qml +++ b/interface/resources/qml/windows/DefaultFrame.qml @@ -1,69 +1,21 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 +import QtGraphicalEffects 1.0 import "." -import "../controls" +import "../styles-uit" Frame { - id: frame - - property bool wideTopMargin: (window && (window.closable || window.title)); - - Rectangle { - anchors { margins: -iconSize; topMargin: -iconSize * (wideTopMargin ? 2 : 1); } - anchors.fill: parent; - color: "#7f7f7f7f"; - radius: 3; - - // Allow dragging of the window - MouseArea { - anchors.fill: parent - drag.target: window - } - - Row { - id: controlsRow - anchors { right: parent.right; top: parent.top; rightMargin: iconSize; topMargin: iconSize / 2; } - spacing: iconSize / 4 - FontAwesome { - visible: false - text: "\uf08d" - style: Text.Outline; styleColor: "white" - size: frame.iconSize - rotation: !frame.parent ? 90 : frame.parent.pinned ? 0 : 90 - color: frame.pinned ? "red" : "black" - MouseArea { - anchors.fill: parent - propagateComposedEvents: true - onClicked: { frame.pin(); mouse.accepted = false; } - } - } - FontAwesome { - visible: window ? window.closable : false - text: closeClickArea.containsMouse ? "\uf057" : "\uf05c" - style: Text.Outline; - styleColor: "white" - color: closeClickArea.containsMouse ? "red" : "black" - size: frame.iconSize - MouseArea { - id: closeClickArea - anchors.fill: parent - hoverEnabled: true - onClicked: window.visible = false; - } - } - } - - Text { - id: titleText - anchors { left: parent.left; leftMargin: iconSize; right: controlsRow.left; rightMargin: iconSize; top: parent.top; topMargin: iconSize / 2; } - text: window ? window.title : "" - elide: Text.ElideRight - font.bold: true - color: (window && window.focus) ? "white" : "gray" - style: Text.Outline; - styleColor: "black" - } - } - + HifiConstants { id: hifi } + DefaultFrameDecoration {} } diff --git a/interface/resources/qml/windows/DefaultFrameDecoration.qml b/interface/resources/qml/windows/DefaultFrameDecoration.qml new file mode 100644 index 0000000000..ce47b818b1 --- /dev/null +++ b/interface/resources/qml/windows/DefaultFrameDecoration.qml @@ -0,0 +1,113 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + +Decoration { + HifiConstants { id: hifi } + + // Dialog frame + id: root + + property int iconSize: hifi.dimensions.frameIconSize + frameMargin: 9 + frameMarginLeft: frameMargin + frameMarginRight: frameMargin + frameMarginTop: 2 * frameMargin + iconSize + frameMarginBottom: iconSize + 11 + + onInflateDecorations: { + if (!HMD.active) { + return; + } + root.frameMargin = 18 + titleText.size = hifi.fontSizes.overlayTitle * 2 + root.iconSize = hifi.dimensions.frameIconSize * 2 + } + + onDeflateDecorations: { + root.frameMargin = 9 + titleText.size = hifi.fontSizes.overlayTitle + root.iconSize = hifi.dimensions.frameIconSize + } + + + Row { + id: controlsRow + anchors { + right: parent.right; + top: parent.top; + topMargin: root.frameMargin + 1 // Move down a little to visually align with the title + rightMargin: root.frameMarginRight; + } + spacing: root.iconSize / 4 + + HiFiGlyphs { + // "Pin" button + visible: window.pinnable + text: window.pinned ? hifi.glyphs.pinInverted : hifi.glyphs.pin + color: pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white + size: root.iconSize + MouseArea { + id: pinClickArea + anchors.fill: parent + hoverEnabled: true + propagateComposedEvents: true + onClicked: window.pinned = !window.pinned; + } + } + + HiFiGlyphs { + // "Close" button + visible: window ? window.closable : false + text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close + color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white + size: root.iconSize + MouseArea { + id: closeClickArea + anchors.fill: parent + hoverEnabled: true + onClicked: window.shown = false; + } + } + } + + RalewayRegular { + // Title + id: titleText + anchors { + left: parent.left + leftMargin: root.frameMarginLeft + hifi.dimensions.contentMargin.x + right: controlsRow.left + rightMargin: root.iconSize + top: parent.top + topMargin: root.frameMargin + } + text: window ? window.title : "" + color: hifi.colors.white + size: hifi.fontSizes.overlayTitle + } + + DropShadow { + source: titleText + anchors.fill: titleText + horizontalOffset: 2 + verticalOffset: 2 + samples: 2 + color: hifi.colors.baseGrayShadow60 + visible: (window && window.focus) + cached: true + } +} + diff --git a/interface/resources/qml/windows/Fadable.qml b/interface/resources/qml/windows/Fadable.qml index 0352966bd0..38cd4bf1f9 100644 --- a/interface/resources/qml/windows/Fadable.qml +++ b/interface/resources/qml/windows/Fadable.qml @@ -1,8 +1,18 @@ +// +// Fadable.qml +// +// Created by Bradley Austin Davis on 15 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import QtQuick.Controls 1.4 import QtGraphicalEffects 1.0 -import "." -import "../styles" + +import "../styles-uit" // Enable window visibility transitions FocusScope { @@ -13,6 +23,7 @@ FocusScope { fadeTargetProperty = visible ? 1.0 : 0.0 } + property var completionCallback; // The target property to animate, usually scale or opacity property alias fadeTargetProperty: root.opacity // always start the property at 0 to enable fade in on creation @@ -33,6 +44,13 @@ FocusScope { fadeTargetProperty = target ? 1.0 : 0.0; return; } + + // Now handle completions + if (completionCallback) { + completionCallback(); + completionCallback = undefined; + } + } // The actual animator @@ -43,8 +61,17 @@ FocusScope { } } - // Once we're transparent, disable the dialog's visibility onFadeTargetPropertyChanged: { visible = (fadeTargetProperty != 0.0); } + + function fadeIn(callback) { + completionCallback = callback; + fadeTargetProperty = 1.0; + } + + function fadeOut(callback) { + completionCallback = callback; + fadeTargetProperty = 0.0; + } } diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index 20bf669b9a..030af974f6 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -1,24 +1,42 @@ -import QtQuick 2.5 +// +// Frame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -import "../controls" +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "../styles-uit" import "../js/Utils.js" as Utils Item { id: frame + HifiConstants { id: hifi } + + default property var decoration + + property bool gradientsSupported: desktop.gradientsSupported + + readonly property int frameMarginLeft: frame.decoration ? frame.decoration.frameMarginLeft : 0 + readonly property int frameMarginRight: frame.decoration ? frame.decoration.frameMarginRight : 0 + readonly property int frameMarginTop: frame.decoration ? frame.decoration.frameMarginTop : 0 + readonly property int frameMarginBottom: frame.decoration ? frame.decoration.frameMarginBottom : 0 + // Frames always fill their parents, but their decorations may extend // beyond the window via negative margin sizes anchors.fill: parent - // Convenience accessor for the window - property alias window: frame.parent - readonly property int iconSize: 24 - default property var decoration; - children: [ + focusShadow, decoration, sizeOutline, debugZ, - sizeDrag, + sizeDrag ] Text { @@ -35,28 +53,50 @@ Item { window.height = newSize.y } + RadialGradient { + id: focusShadow + width: 1.66 * window.width + height: 1.66 * window.height + x: (window.width - width) / 2 + y: window.height / 2 - 0.375 * height + visible: gradientsSupported && window && window.focus && window.content.visible + gradient: Gradient { + // GradientStop position 0.5 is at full circumference of circle that fits inside the square. + GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity + GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity + GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity + GradientStop { position: 1.0; color: "#00000000" } + } + cached: true + } + Rectangle { id: sizeOutline - width: window ? window.width : 0 - height: window ? window.height : 0 - color: "#00000000" - border.width: 4 - radius: 10 + x: -frameMarginLeft + y: -frameMarginTop + width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0 + height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0 + color: hifi.colors.baseGrayHighlight15 + border.width: 3 + border.color: hifi.colors.white50 + radius: hifi.dimensions.borderRadius visible: window ? !window.content.visible : false } MouseArea { + // Resize handle id: sizeDrag - width: iconSize - height: iconSize + width: hifi.dimensions.frameIconSize + height: hifi.dimensions.frameIconSize enabled: window ? window.resizable : false - x: window ? window.width : 0 - y: window ? window.height : 0 + hoverEnabled: true + x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0 + y: window ? window.height + 4 : 0 property vector2d pressOrigin property vector2d sizeOrigin property bool hid: false onPressed: { - console.log("Pressed on size") + //console.log("Pressed on size") pressOrigin = Qt.vector2d(mouseX, mouseY) sizeOrigin = Qt.vector2d(window.content.width, window.content.height) hid = false; @@ -77,15 +117,15 @@ Item { frame.deltaSize(delta.x, delta.y) } } - FontAwesome { + HiFiGlyphs { visible: sizeDrag.enabled - rotation: -45 - anchors { centerIn: parent } - horizontalAlignment: Text.AlignHCenter - text: "\uf07d" - size: iconSize / 3 * 2 - style: Text.Outline; styleColor: "white" + x: -11 // Move a little to visually align + y: window.modality == Qt.ApplicationModal ? -6 : -4 + text: hifi.glyphs.resizeHandle + size: hifi.dimensions.frameIconSize + 10 + color: sizeDrag.containsMouse || sizeDrag.pressed + ? hifi.colors.white + : (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80) } } - } diff --git a/interface/resources/qml/windows/HiddenFrame.qml b/interface/resources/qml/windows/HiddenFrame.qml index 2621b71eed..3d3fd047e2 100644 --- a/interface/resources/qml/windows/HiddenFrame.qml +++ b/interface/resources/qml/windows/HiddenFrame.qml @@ -2,7 +2,7 @@ import QtQuick 2.5 import "." -Frame { +Item { id: frame Item { anchors.fill: parent } diff --git a/interface/resources/qml/windows/ModalFrame.qml b/interface/resources/qml/windows/ModalFrame.qml index eb4641bc75..211353b5f3 100644 --- a/interface/resources/qml/windows/ModalFrame.qml +++ b/interface/resources/qml/windows/ModalFrame.qml @@ -1,36 +1,98 @@ +// +// ModalFrame.qml +// +// Created by Bradley Austin Davis on 15 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import "." -import "../controls" +import "../controls-uit" +import "../styles-uit" Frame { - id: frame + HifiConstants { id: hifi } - Item { - anchors.fill: parent + Rectangle { + id: frameContent - Rectangle { - id: background - anchors.fill: parent - anchors.margins: -4096 - visible: window.visible - color: "#7f7f7f7f"; - radius: 3; + readonly property bool hasTitle: window.title != "" + + readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x + readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x + readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) + readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y + + signal frameClicked(); + + anchors { + fill: parent + topMargin: -frameMarginTop + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + bottomMargin: -frameMarginBottom } - Text { - y: -implicitHeight - iconSize / 2 - text: window.title - elide: Text.ElideRight - font.bold: true - color: window.focus ? "white" : "gray" - style: Text.Outline; - styleColor: "black" + border { + width: hifi.dimensions.borderWidth + color: hifi.colors.lightGrayText80 + } + radius: hifi.dimensions.borderRadius + color: hifi.colors.faintGray + + // Enable dragging of the window + MouseArea { + anchors.fill: parent + drag.target: window + enabled: window.draggable + onClicked: window.frameClicked(); + } + + Item { + visible: frameContent.hasTitle + anchors.fill: parent + anchors { + topMargin: -parent.anchors.topMargin + leftMargin: -parent.anchors.leftMargin + rightMargin: -parent.anchors.rightMargin + } + + Item { + width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0) + x: (parent.width - width) / 2 + + onWidthChanged: window.titleWidth = width + + HiFiGlyphs { + id: icon + text: window.iconText ? window.iconText : "" + size: window.iconSize ? window.iconSize : 30 + color: hifi.colors.lightGray + visible: text != "" + anchors.verticalCenter: title.verticalCenter + anchors.left: parent.left + } + RalewayRegular { + id: title + text: window.title + elide: Text.ElideRight + color: hifi.colors.baseGrayHighlight + size: hifi.fontSizes.overlayTitle + y: -hifi.dimensions.modalDialogTitleHeight + anchors.right: parent.right + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: hifi.colors.lightGray + } } } - - - - } - diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml index 32443e70e3..2d56099051 100644 --- a/interface/resources/qml/windows/ModalWindow.qml +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -1,14 +1,28 @@ +// +// ModalWindow.qml +// +// Created by Bradley Austin Davis on 22 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import "." -Window { - id: root - anchors.centerIn: parent +ScrollingWindow { + id: window modality: Qt.ApplicationModal destroyOnCloseButton: true - destroyOnInvisible: true - frame: ModalFrame{} + destroyOnHidden: true + frame: ModalFrame { } + + property int colorScheme: hifi.colorSchemes.light + property bool draggable: false + + signal frameClicked(); + + anchors.centerIn: draggable ? undefined : parent } - - diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml new file mode 100644 index 0000000000..f1dc744344 --- /dev/null +++ b/interface/resources/qml/windows/ScrollingWindow.qml @@ -0,0 +1,157 @@ +// +// Window.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + +// FIXME how do I set the initial position of a window without +// overriding places where the a individual client of the window +// might be setting the position with a Settings{} element? + +// FIXME how to I enable dragging without allowing the window to lay outside +// of the desktop? How do I ensure when the desktop resizes all the windows +// are still at least partially visible? +Window { + id: window + HifiConstants { id: hifi } + children: [ swallower, frame, pane, activator ] + + property var footer: Item { } // Optional static footer at the bottom of the dialog. + + // Scrollable window content. + // FIXME this should not define any visual content in this type. The base window + // type should only consist of logic sized areas, with nothing drawn (although the + // default value for the frame property does include visual decorations) + property var pane: Item { + property bool isScrolling: scrollView.height < scrollView.contentItem.height + property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) + property int scrollHeight: scrollView.height + + anchors.fill: parent + anchors.rightMargin: isScrolling ? 11 : 0 + + Rectangle { + id: contentBackground + anchors.fill: parent + anchors.rightMargin: parent.isScrolling ? 11 : 0 + color: hifi.colors.baseGray + visible: !window.hideBackground && modality != Qt.ApplicationModal + } + + + LinearGradient { + visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal + anchors.top: contentBackground.bottom + anchors.left: contentBackground.left + width: contentBackground.width - 1 + height: 4 + start: Qt.point(0, 0) + end: Qt.point(0, 4) + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.darkGray } + GradientStop { position: 1.0; color: hifi.colors.darkGray0 } + } + cached: true + } + + ScrollView { + id: scrollView + contentItem: content + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + anchors.fill: parent + anchors.rightMargin: parent.isScrolling ? 1 : 0 + anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0 + + style: ScrollViewStyle { + + padding.right: -7 // Move to right away from content. + + handle: Item { + implicitWidth: 8 + Rectangle { + radius: 4 + color: hifi.colors.white30 + anchors { + fill: parent + leftMargin: 2 // Finesse size and position. + topMargin: 1 + bottomMargin: 1 + } + } + } + + scrollBarBackground: Item { + implicitWidth: 10 + Rectangle { + color: hifi.colors.darkGray30 + radius: 4 + anchors { + fill: parent + topMargin: -1 // Finesse size + bottomMargin: -2 + } + } + } + + incrementControl: Item { + visible: false + } + + decrementControl: Item { + visible: false + } + } + } + + Rectangle { + // Optional non-scrolling footer. + id: footerPane + anchors { + left: parent.left + bottom: parent.bottom + } + width: parent.contentWidth + height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3 + color: hifi.colors.baseGray + visible: footer.height > 0 + + Item { + // Horizontal rule. + anchors.fill: parent + + Rectangle { + width: parent.width + height: 1 + y: 1 // Stop displaying content just above horizontal rule/=. + color: hifi.colors.baseGrayShadow + } + + Rectangle { + width: parent.width + height: 1 + y: 2 + color: hifi.colors.baseGrayHighlight + } + } + + Item { + anchors.fill: parent + anchors.topMargin: 3 // Horizontal rule. + children: [ footer ] + } + } + } +} diff --git a/interface/resources/qml/windows/ToolFrame.qml b/interface/resources/qml/windows/ToolFrame.qml new file mode 100644 index 0000000000..20c86afb5e --- /dev/null +++ b/interface/resources/qml/windows/ToolFrame.qml @@ -0,0 +1,26 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + +Frame { + HifiConstants { id: hifi } + property alias horizontalSpacers: decoration.horizontalSpacers + property alias verticalSpacers: decoration.verticalSpacers + + ToolFrameDecoration { + id: decoration + } +} + diff --git a/interface/resources/qml/windows/ToolFrameDecoration.qml b/interface/resources/qml/windows/ToolFrameDecoration.qml new file mode 100644 index 0000000000..ba36a2a38c --- /dev/null +++ b/interface/resources/qml/windows/ToolFrameDecoration.qml @@ -0,0 +1,96 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + +Decoration { + id: root + HifiConstants { id: hifi } + + property bool horizontalSpacers: false + property bool verticalSpacers: false + + // Dialog frame + property int spacerWidth: 8 + property int spacerRadius: 4 + property int spacerMargin: 12 + frameMargin: 6 + frameMarginLeft: frameMargin + (horizontalSpacers ? spacerMargin : 0) + frameMarginRight: frameMargin + (horizontalSpacers ? spacerMargin : 0) + frameMarginTop: frameMargin + (verticalSpacers ? spacerMargin : 0) + frameMarginBottom: frameMargin + (verticalSpacers ? spacerMargin : 0) + radius: hifi.dimensions.borderRadius / 2 + + onInflateDecorations: { + if (!HMD.active) { + return; + } + root.frameMargin = 18 + root.spacerWidth = 16 + root.spacerRadius = 8 + root.spacerMargin = 8 + } + + onDeflateDecorations: { + root.frameMargin = 6 + root.spacerWidth = 8 + root.spacerRadius = 4 + root.spacerMargin = 12 + } + + Rectangle { + visible: horizontalSpacers + anchors.left: parent.left + anchors.leftMargin: 6 + anchors.verticalCenter: parent.verticalCenter + width: root.spacerWidth + height: decoration.height - 12 + color: "gray"; + radius: root.spacerRadius + } + + Rectangle { + visible: horizontalSpacers + anchors.right: parent.right + anchors.rightMargin: 6 + anchors.verticalCenter: parent.verticalCenter + width: root.spacerWidth + height: decoration.height - 12 + color: "gray"; + radius: root.spacerRadius + } + + Rectangle { + visible: verticalSpacers + anchors.top: parent.top + anchors.topMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + height: root.spacerWidth + width: decoration.width - 12 + color: "gray"; + radius: root.spacerRadius + } + + Rectangle { + visible: verticalSpacers + anchors.bottom: parent.bottom + anchors.bottomMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + height: root.spacerWidth + width: decoration.width - 12 + color: "gray"; + radius: root.spacerRadius + } +} + diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 3abdbacc64..c873872692 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -1,9 +1,20 @@ +// +// Window.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.0 import "." -import "../styles" +import "../styles-uit" // FIXME how do I set the initial position of a window without // overriding places where the a individual client of the window @@ -15,16 +26,38 @@ import "../styles" Fadable { id: window HifiConstants { id: hifi } + + // + // Signals + // + signal windowDestroyed(); + signal mouseEntered(); + signal mouseExited(); + + // + // Native properties + // + // The Window size is the size of the content, while the frame // decorations can extend outside it. implicitHeight: content ? content.height : 0 implicitWidth: content ? content.width : 0 x: desktop.invalid_position; y: desktop.invalid_position; - enabled: visible + children: [ swallower, frame, content, activator ] - signal windowDestroyed(); + // + // Custom properties + // property int modality: Qt.NonModal + // Corresponds to the window shown / hidden state AS DISTINCT from window visibility. + // Window visibility should NOT be used as a proxy for any other behavior. + property bool shown: true + // FIXME workaround to deal with the face that some visual items are defined here, + // when they should be moved to a frame derived type + property bool hideBackground: false + visible: shown + enabled: visible readonly property bool topLevelWindow: true property string title // Should the window be closable control? @@ -34,17 +67,23 @@ Fadable { // Should hitting the close button hide or destroy the window? property bool destroyOnCloseButton: true // Should hiding the window destroy it or just hide it? - property bool destroyOnInvisible: false - // FIXME support for pinned / unpinned pending full design - // property bool pinnable: false - // property bool pinned: false + property bool destroyOnHidden: false + property bool pinnable: true + property bool pinned: false property bool resizable: false + property bool gradientsSupported: desktop.gradientsSupported + property int colorScheme: hifi.colorSchemes.dark + property vector2d minSize: Qt.vector2d(100, 100) - property vector2d maxSize: Qt.vector2d(1280, 720) + property vector2d maxSize: Qt.vector2d(1280, 800) // The content to place inside the window, determined by the client default property var content + property var footer: Item { } // Optional static footer at the bottom of the dialog. + + function setDefaultFocus() {} // Default function; can be overridden by dialogs. + property var rectifier: Timer { property bool executing: false; interval: 100 @@ -65,24 +104,18 @@ Fadable { } } - - onXChanged: rectifier.begin(); - onYChanged: rectifier.begin(); - // This mouse area serves to raise the window. To function, it must live // in the window and have a higher Z-order than the content, but follow // the position and size of frame decoration property var activator: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.margins - y: frame.decoration.anchors.topMargin + width: frame.decoration ? frame.decoration.width : window.width + height: frame.decoration ? frame.decoration.height : window.height + x: frame.decoration ? frame.decoration.anchors.leftMargin : 0 + y: frame.decoration ? frame.decoration.anchors.topMargin : 0 propagateComposedEvents: true - hoverEnabled: true acceptedButtons: Qt.AllButtons enabled: window.visible onPressed: { - //console.log("Pressed on activator area"); window.raise(); mouse.accepted = false; } @@ -92,10 +125,10 @@ Fadable { // to prevent things like mouse wheel events from reaching the application and changing // the camera if the user is scrolling through a list and gets to the end. property var swallower: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.margins - y: frame.decoration.anchors.topMargin + width: frame.decoration ? frame.decoration.width : window.width + height: frame.decoration ? frame.decoration.height : window.height + x: frame.decoration ? frame.decoration.anchors.leftMargin : 0 + y: frame.decoration ? frame.decoration.anchors.topMargin : 0 hoverEnabled: true acceptedButtons: Qt.AllButtons enabled: window.visible @@ -106,71 +139,122 @@ Fadable { onWheel: {} } - // Default to a standard frame. Can be overriden to provide custom // frame styles, like a full desktop frame to simulate a modal window - property var frame: DefaultFrame { } + property var frame: DefaultFrame { + //window: window + } - children: [ swallower, frame, content, activator ] - + // + // Handlers + // Component.onCompleted: { window.parentChanged.connect(raise); - raise(); - centerOrReposition(); + setDefaultFocus(); + d.centerOrReposition(); + d.updateVisibility(shown); } Component.onDestruction: { window.parentChanged.disconnect(raise); // Prevent warning on shutdown windowDestroyed(); } - function centerOrReposition() { - if (x == desktop.invalid_position && y == desktop.invalid_position) { - desktop.centerOnVisible(window); - } else { - desktop.repositionOnVisible(window); - } - } + onXChanged: rectifier.begin(); + onYChanged: rectifier.begin(); + + onShownChanged: d.updateVisibility(shown) onVisibleChanged: { - if (!visible && destroyOnInvisible) { - destroy(); - return; - } - if (visible) { - raise(); - } enabled = visible - if (visible && parent) { - centerOrReposition(); + d.centerOrReposition(); } } + QtObject { + id: d + + readonly property alias pinned: window.pinned + readonly property alias shown: window.shown + readonly property alias modality: window.modality; + + function getTargetVisibility() { + if (!window.shown) { + return false; + } + + if (modality !== Qt.NonModal) { + return true; + } + + if (pinned) { + return true; + } + + if (desktop && !desktop.pinned) { + return true; + } + + return false; + } + + // The force flag causes all windows to fade back in, because a window was shown + readonly property alias visible: window.visible + function updateVisibility(force) { + if (force && !pinned && desktop.pinned) { + // Change the pinned state (which in turn will call us again) + desktop.pinned = false; + return; + } + + var targetVisibility = getTargetVisibility(); + if (targetVisibility === visible) { + if (force) { + window.raise(); + } + return; + } + + if (targetVisibility) { + fadeIn(function() { + if (force) { + window.raise(); + } + }); + } else { + fadeOut(function() { + if (!window.shown && window.destroyOnHidden) { + window.destroy(); + } + }); + } + } + + function centerOrReposition() { + if (x == desktop.invalid_position && y == desktop.invalid_position) { + desktop.centerOnVisible(window); + } else { + desktop.repositionOnVisible(window); + } + } + + } + + // When the desktop pinned state changes, automatically handle the current windows + Connections { target: desktop; onPinnedChanged: d.updateVisibility() } + + function raise() { if (visible && parent) { desktop.raise(window) } } - function pin() { -// pinned = ! pinned + function setPinned() { + pinned = !pinned } - // our close function performs the same way as the OffscreenUI class: - // don't do anything but manipulate the targetVisible flag and let the other - // mechanisms decide if the window should be destroyed after the close - // animation completes - // FIXME using this close function messes up the visibility signals received by the - // type and it's derived types -// function close() { -// console.log("Closing " + window) -// if (destroyOnCloseButton) { -// destroyOnInvisible = true -// } -// visible = false; -// } - function framedRect() { if (!frame || !frame.decoration) { return Qt.rect(0, 0, window.width, window.height) @@ -180,7 +264,6 @@ Fadable { window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin) } - Keys.onPressed: { switch(event.key) { case Qt.Key_Control: @@ -189,10 +272,9 @@ Fadable { case Qt.Key_Alt: break; - case Qt.Key_W: if (window.closable && (event.modifiers === Qt.ControlModifier)) { - visible = false + shown = false event.accepted = true } // fall through @@ -206,4 +288,7 @@ Fadable { break; } } + + onMouseEntered: console.log("Mouse entered " + window) + onMouseExited: console.log("Mouse exited " + window) } diff --git a/interface/resources/shaders/hmd_hand_lasers.frag b/interface/resources/shaders/hmd_hand_lasers.frag new file mode 100644 index 0000000000..6fffb1c521 --- /dev/null +++ b/interface/resources/shaders/hmd_hand_lasers.frag @@ -0,0 +1,35 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// + +#version 410 core + +uniform vec4 color = vec4(1.0, 1.0, 1.0, 1.0); + +layout(location = 0) in vec3 inLineDistance; + +out vec4 FragColor; + +void main() { + vec2 d = inLineDistance.xy; + d.y = abs(d.y); + d.x = abs(d.x); + if (d.x > 1.0) { + d.x = (d.x - 1.0) / 0.02; + } else { + d.x = 0.0; + } + float alpha = 1.0 - length(d); + if (alpha <= 0.0) { + discard; + } + alpha = pow(alpha, 10.0); + if (alpha < 0.05) { + discard; + } + FragColor = vec4(color.rgb, alpha); +} diff --git a/interface/resources/shaders/hmd_hand_lasers.geom b/interface/resources/shaders/hmd_hand_lasers.geom new file mode 100644 index 0000000000..16b5dafadd --- /dev/null +++ b/interface/resources/shaders/hmd_hand_lasers.geom @@ -0,0 +1,70 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// +#version 410 core +#extension GL_EXT_geometry_shader4 : enable + +layout(location = 0) out vec3 outLineDistance; + +layout(lines) in; +layout(triangle_strip, max_vertices = 24) out; + +vec3[2] getOrthogonals(in vec3 n, float scale) { + float yDot = abs(dot(n, vec3(0, 1, 0))); + + vec3 result[2]; + if (yDot < 0.9) { + result[0] = normalize(cross(n, vec3(0, 1, 0))); + } else { + result[0] = normalize(cross(n, vec3(1, 0, 0))); + } + // The cross of result[0] and n is orthogonal to both, which are orthogonal to each other + result[1] = cross(result[0], n); + result[0] *= scale; + result[1] *= scale; + return result; +} + + +vec2 orthogonal(vec2 v) { + vec2 result = v.yx; + result.y *= -1.0; + return result; +} + +void main() { + vec2 endpoints[2]; + for (int i = 0; i < 2; ++i) { + endpoints[i] = gl_PositionIn[i].xy / gl_PositionIn[i].w; + } + vec2 lineNormal = normalize(endpoints[1] - endpoints[0]); + vec2 lineOrthogonal = orthogonal(lineNormal); + lineNormal *= 0.02; + lineOrthogonal *= 0.02; + + gl_Position = gl_PositionIn[0]; + gl_Position.xy -= lineOrthogonal; + outLineDistance = vec3(-1.02, -1, gl_Position.z); + EmitVertex(); + + gl_Position = gl_PositionIn[0]; + gl_Position.xy += lineOrthogonal; + outLineDistance = vec3(-1.02, 1, gl_Position.z); + EmitVertex(); + + gl_Position = gl_PositionIn[1]; + gl_Position.xy -= lineOrthogonal; + outLineDistance = vec3(1.02, -1, gl_Position.z); + EmitVertex(); + + gl_Position = gl_PositionIn[1]; + gl_Position.xy += lineOrthogonal; + outLineDistance = vec3(1.02, 1, gl_Position.z); + EmitVertex(); + + EndPrimitive(); +} diff --git a/interface/resources/shaders/hmd_hand_lasers.vert b/interface/resources/shaders/hmd_hand_lasers.vert new file mode 100644 index 0000000000..db5c7c1ecd --- /dev/null +++ b/interface/resources/shaders/hmd_hand_lasers.vert @@ -0,0 +1,15 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// +#version 410 core +uniform mat4 mvp = mat4(1); + +in vec3 Position; + +void main() { + gl_Position = mvp * vec4(Position, 1); +} diff --git a/interface/resources/shaders/hmd_reproject.frag b/interface/resources/shaders/hmd_reproject.frag new file mode 100644 index 0000000000..adda0315a3 --- /dev/null +++ b/interface/resources/shaders/hmd_reproject.frag @@ -0,0 +1,78 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// + +#version 410 core + +uniform sampler2D sampler; +uniform mat3 reprojection = mat3(1); +uniform mat4 inverseProjections[2]; +uniform mat4 projections[2]; + +in vec2 vTexCoord; +in vec3 vPosition; + +out vec4 FragColor; + +void main() { + vec2 uv = vTexCoord; + + mat4 eyeInverseProjection; + mat4 eyeProjection; + + float xoffset = 1.0; + vec2 uvmin = vec2(0.0); + vec2 uvmax = vec2(1.0); + // determine the correct projection and inverse projection to use. + if (vTexCoord.x < 0.5) { + uvmax.x = 0.5; + eyeInverseProjection = inverseProjections[0]; + eyeProjection = projections[0]; + } else { + xoffset = -1.0; + uvmin.x = 0.5; + uvmax.x = 1.0; + eyeInverseProjection = inverseProjections[1]; + eyeProjection = projections[1]; + } + + // Account for stereo in calculating the per-eye NDC coordinates + vec4 ndcSpace = vec4(vPosition, 1.0); + ndcSpace.x *= 2.0; + ndcSpace.x += xoffset; + + // Convert from NDC to eyespace + vec4 eyeSpace = eyeInverseProjection * ndcSpace; + eyeSpace /= eyeSpace.w; + + // Convert to a noramlized ray + vec3 ray = eyeSpace.xyz; + ray = normalize(ray); + + // Adjust the ray by the rotation + ray = reprojection * ray; + + // Project back on to the texture plane + ray *= eyeSpace.z / ray.z; + + // Update the eyespace vector + eyeSpace.xyz = ray; + + // Reproject back into NDC + ndcSpace = eyeProjection * eyeSpace; + ndcSpace /= ndcSpace.w; + ndcSpace.x -= xoffset; + ndcSpace.x /= 2.0; + + // Calculate the new UV coordinates + uv = (ndcSpace.xy / 2.0) + 0.5; + if (any(greaterThan(uv, uvmax)) || any(lessThan(uv, uvmin))) { + FragColor = vec4(0.0, 0.0, 0.0, 1.0); + } else { + FragColor = texture(sampler, uv); + } +} \ No newline at end of file diff --git a/interface/resources/shaders/hmd_reproject.vert b/interface/resources/shaders/hmd_reproject.vert new file mode 100644 index 0000000000..923375613a --- /dev/null +++ b/interface/resources/shaders/hmd_reproject.vert @@ -0,0 +1,20 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// + +#version 410 core +in vec3 Position; +in vec2 TexCoord; + +out vec3 vPosition; +out vec2 vTexCoord; + +void main() { + gl_Position = vec4(Position, 1); + vTexCoord = TexCoord; + vPosition = Position; +} diff --git a/interface/resources/shaders/hmd_ui_glow.frag b/interface/resources/shaders/hmd_ui_glow.frag new file mode 100644 index 0000000000..733f32d718 --- /dev/null +++ b/interface/resources/shaders/hmd_ui_glow.frag @@ -0,0 +1,65 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// + +#version 410 core + +uniform sampler2D sampler; +uniform float alpha = 1.0; +uniform vec4 glowPoints = vec4(-1); +uniform vec4 glowColors[2]; +uniform vec2 resolution = vec2(3960.0, 1188.0); +uniform float radius = 0.005; + +in vec3 vPosition; +in vec2 vTexCoord; +in vec4 vGlowPoints; + +out vec4 FragColor; + +float easeInOutCubic(float f) { + const float d = 1.0; + const float b = 0.0; + const float c = 1.0; + float t = f; + if ((t /= d / 2.0) < 1.0) return c / 2.0 * t * t * t + b; + return c / 2.0 * ((t -= 2.0) * t * t + 2.0) + b; +} + +void main() { + vec2 aspect = resolution; + aspect /= resolution.x; + FragColor = texture(sampler, vTexCoord); + + float glowIntensity = 0.0; + float dist1 = distance(vTexCoord * aspect, glowPoints.xy * aspect); + float dist2 = distance(vTexCoord * aspect, glowPoints.zw * aspect); + float dist = min(dist1, dist2); + vec3 glowColor = glowColors[0].rgb; + if (dist2 < dist1) { + glowColor = glowColors[1].rgb; + } + + if (dist <= radius) { + glowIntensity = 1.0 - (dist / radius); + glowColor.rgb = pow(glowColor, vec3(1.0 - glowIntensity)); + glowIntensity = easeInOutCubic(glowIntensity); + glowIntensity = pow(glowIntensity, 0.5); + } + + if (alpha <= 0.0) { + if (glowIntensity <= 0.0) { + discard; + } + + FragColor = vec4(glowColor, glowIntensity); + return; + } + + FragColor.rgb = mix(FragColor.rgb, glowColor.rgb, glowIntensity); + FragColor.a *= alpha; +} \ No newline at end of file diff --git a/interface/resources/shaders/hmd_ui_glow.vert b/interface/resources/shaders/hmd_ui_glow.vert new file mode 100644 index 0000000000..5defec085f --- /dev/null +++ b/interface/resources/shaders/hmd_ui_glow.vert @@ -0,0 +1,23 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// + +#version 410 core + +uniform mat4 mvp = mat4(1); + +in vec3 Position; +in vec2 TexCoord; + +out vec3 vPosition; +out vec2 vTexCoord; + +void main() { + gl_Position = mvp * vec4(Position, 1); + vTexCoord = TexCoord; + vPosition = Position; +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5f08877ae2..336e0a018e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -67,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -82,8 +84,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -96,6 +98,7 @@ #include #include #include +#include #include #include #include @@ -108,7 +111,6 @@ #include "audio/AudioScope.h" #include "avatar/AvatarManager.h" #include "CrashHandler.h" -#include "input-plugins/SpacemouseManager.h" #include "devices/DdeFaceTracker.h" #include "devices/EyeTracker.h" #include "devices/Faceshift.h" @@ -119,7 +121,6 @@ #include "InterfaceLogging.h" #include "LODManager.h" #include "ModelPackager.h" -#include "PluginContainerProxy.h" #include "scripting/AccountScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/AudioDeviceScriptingInterface.h" @@ -133,6 +134,7 @@ #include "scripting/WebWindowClass.h" #include "scripting/WindowScriptingInterface.h" #include "scripting/ControllerScriptingInterface.h" +#include "scripting/ToolbarScriptingInterface.h" #include "scripting/RatesScriptingInterface.h" #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" @@ -151,6 +153,8 @@ #include "InterfaceParentFinder.h" #include "FrameTimingsScriptingInterface.h" +#include +#include // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -166,16 +170,24 @@ static QTimer locationUpdateTimer; static QTimer identityPacketTimer; static QTimer pingTimer; +static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16; + +// For processing on QThreadPool, target 2 less than the ideal number of threads, leaving +// 2 logical cores available for time sensitive tasks. +static const int MIN_PROCESSING_THREAD_POOL_SIZE = 2; +static const int PROCESSING_THREAD_POOL_SIZE = std::max(MIN_PROCESSING_THREAD_POOL_SIZE, + QThread::idealThreadCount() - 2); + static const QString SNAPSHOT_EXTENSION = ".jpg"; static const QString SVO_EXTENSION = ".svo"; -static const QString SVO_JSON_EXTENSION = ".svo.json"; +static const QString SVO_JSON_EXTENSION = ".svo.json"; +static const QString JSON_EXTENSION = ".json"; static const QString JS_EXTENSION = ".js"; static const QString FST_EXTENSION = ".fst"; static const QString FBX_EXTENSION = ".fbx"; static const QString OBJ_EXTENSION = ".obj"; static const QString AVA_JSON_EXTENSION = ".ava.json"; -static const int MSECS_PER_SEC = 1000; static const int MIRROR_VIEW_TOP_PADDING = 5; static const int MIRROR_VIEW_LEFT_PADDING = 10; static const int MIRROR_VIEW_WIDTH = 265; @@ -201,13 +213,16 @@ static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStanda Setting::Handle maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS); +static const QString MARKETPLACE_CDN_HOSTNAME = "mpassets.highfidelity.com"; + const QHash Application::_acceptedExtensions { { SNAPSHOT_EXTENSION, &Application::acceptSnapshot }, { SVO_EXTENSION, &Application::importSVOFromURL }, { SVO_JSON_EXTENSION, &Application::importSVOFromURL }, + { AVA_JSON_EXTENSION, &Application::askToWearAvatarAttachmentUrl }, + { JSON_EXTENSION, &Application::importJSONFromURL }, { JS_EXTENSION, &Application::askToLoadScript }, - { FST_EXTENSION, &Application::askToSetAvatarUrl }, - { AVA_JSON_EXTENSION, &Application::askToWearAvatarAttachmentUrl } + { FST_EXTENSION, &Application::askToSetAvatarUrl } }; class DeadlockWatchdogThread : public QThread { @@ -290,6 +305,8 @@ public: // Don't actually crash in debug builds, in case this apparent deadlock is simply from // the developer actively debugging code #ifdef NDEBUG + + deadlockDetectionCrash(); #endif } @@ -390,7 +407,10 @@ bool setupEssentials(int& argc, char** argv) { Setting::preInit(); - bool previousSessionCrashed = CrashHandler::checkForResetSettings(); + + static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset"; + bool suppressPrompt = cmdOptionExists(argc, const_cast(argv), SUPPRESS_SETTINGS_RESET); + bool previousSessionCrashed = CrashHandler::checkForResetSettings(suppressPrompt); CrashHandler::writeRunningMarkerFiler(); qAddPostRoutine(CrashHandler::deleteRunningMarkerFile); @@ -402,6 +422,7 @@ bool setupEssentials(int& argc, char** argv) { Setting::init(); // Set dependencies + DependencyManager::set(std::bind(&Application::getUserAgent, qApp)); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -422,6 +443,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -434,7 +456,8 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - + DependencyManager::set(); + DependencyManager::set(); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) DependencyManager::set(); @@ -461,7 +484,6 @@ bool setupEssentials(int& argc, char** argv) { // continuing to overburden Application.cpp Cube3DOverlay* _keyboardFocusHighlight{ nullptr }; int _keyboardFocusHighlightID{ -1 }; -PluginContainer* _pluginContainer; // FIXME hack access to the internal share context for the Chromium helper @@ -501,13 +523,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _maxOctreePPS(maxOctreePacketsPerSecond.get()), _lastFaceTrackerUpdate(0) { - // FIXME this may be excessively conservative. On the other hand - // maybe I'm used to having an 8-core machine - // Perhaps find the ideal thread count and subtract 2 or 3 - // (main thread, present thread, random OS load) - // More threads == faster concurrent loads, but also more concurrent - // load on the GPU until we can serialize GPU transfers (off the main thread) - QThreadPool::globalInstance()->setMaxThreadCount(2); + + + PluginContainer* pluginContainer = dynamic_cast(this); // set the container for any plugins that care + PluginManager::getInstance()->setContainer(pluginContainer); + + QThreadPool::globalInstance()->setMaxThreadCount(PROCESSING_THREAD_POOL_SIZE); thread()->setPriority(QThread::HighPriority); thread()->setObjectName("Main Thread"); @@ -518,7 +539,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _entityClipboard->createRootElement(); - _pluginContainer = new PluginContainerProxy(); #ifdef Q_OS_WIN installNativeEventFilter(&MyNativeEventFilter::getInstance()); #endif @@ -630,9 +650,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); + connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one - const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SEC; + const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SECOND; auto discoverabilityManager = DependencyManager::get(); connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); @@ -652,23 +673,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated); connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID); connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID); - connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); + // you might think we could just do this in NodeList but we only want this connection for Interface + connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); + // connect to appropriate slots on AccountManager - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); auto dialogsManager = DependencyManager::get(); - connect(&accountManager, &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog); - connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle); + connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog); + connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); // set the account manager's root URL and trigger a login request if we don't have the access token - accountManager.setIsAgent(true); - accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); - - // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. - // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. - UserActivityLogger::getInstance().launch(applicationVersion(), _previousSessionCrashed, sessionRunTime.get()); + accountManager->setIsAgent(true); + accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); auto addressManager = DependencyManager::get(); @@ -720,7 +739,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket); identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); - ResourceCache::setRequestLimit(3); + ResourceCache::setRequestLimit(MAX_CONCURRENT_RESOURCE_DOWNLOADS); _glWidget = new GLCanvas(); getApplicationCompositor().setRenderingWidget(_glWidget); @@ -759,8 +778,43 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Make sure we don't time out during slow operations at startup updateHeartbeat(); + + // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. + // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. + auto gpuIdent = GPUIdent::getInstance(); + auto glContextData = getGLContextData(); + QJsonObject properties = { + { "version", applicationVersion() }, + { "previousSessionCrashed", _previousSessionCrashed }, + { "previousSessionRuntime", sessionRunTime.get() }, + { "cpu_architecture", QSysInfo::currentCpuArchitecture() }, + { "kernel_type", QSysInfo::kernelType() }, + { "kernel_version", QSysInfo::kernelVersion() }, + { "os_type", QSysInfo::productType() }, + { "os_version", QSysInfo::productVersion() }, + { "gpu_name", gpuIdent->getName() }, + { "gpu_driver", gpuIdent->getDriver() }, + { "gpu_memory", static_cast(gpuIdent->getMemory()) }, + { "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) }, + { "gl_version", glContextData["version"] }, + { "gl_vender", glContextData["vendor"] }, + { "gl_sl_version", glContextData["slVersion"] }, + { "gl_renderer", glContextData["renderer"] } + }; + auto macVersion = QSysInfo::macVersion(); + if (macVersion != QSysInfo::MV_None) { + properties["os_osx_version"] = QSysInfo::macVersion(); + } + auto windowsVersion = QSysInfo::windowsVersion(); + if (windowsVersion != QSysInfo::WV_None) { + properties["os_win_version"] = QSysInfo::windowsVersion(); + } + UserActivityLogger::getInstance().logAction("launch", properties); + + // Tell our entity edit sender about our known jurisdictions _entityEditSender.setServerJurisdictions(&_entityServerJurisdictions); + _entityEditSender.setMyAvatar(getMyAvatar()); // For now we're going to set the PPS for outbound packets to be super high, this is // probably not the right long term solution. But for now, we're going to do this to @@ -879,17 +933,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : cycleCamera(); } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) { if (!offscreenUi->navigationFocused()) { - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); + toggleMenuUnderReticle(); } } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); - } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) { - if (!offscreenUi->navigationFocused()) { - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); - } + toggleMenuUnderReticle(); } else if (action == controller::toInt(controller::Action::RETICLE_X)) { auto oldPos = getApplicationCompositor().getReticlePosition(); getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y }); @@ -917,8 +964,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : return DependencyManager::get()->navigationFocused() ? 1 : 0; }); - // Setup the keyboardMouseDevice and the user input mapper with the default bindings + // Setup the _keyboardMouseDevice, _touchscreenDevice and the user input mapper with the default bindings userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice()); + // if the _touchscreenDevice is not supported it will not be registered + if (_touchscreenDevice) { + userInputMapper->registerDevice(_touchscreenDevice->getInputDevice()); + } // force the model the look at the correct directory (weird order of operations issue) scriptEngines->setScriptsLocation(scriptEngines->getScriptsLocation()); @@ -928,6 +979,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : updateHeartbeat(); loadSettings(); + + // Now that we've loaded the menu and thus switched to the previous display plugin + // we can unlock the desktop repositioning code, since all the positions will be + // relative to the desktop size for this plugin + auto offscreenUi = DependencyManager::get(); + offscreenUi->getDesktop()->setProperty("repositionLocked", false); + // Make sure we don't time out during slow operations at startup updateHeartbeat(); @@ -991,12 +1049,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : if (_keyboardFocusedItem != entityItemID) { _keyboardFocusedItem = UNKNOWN_ENTITY_ID; auto properties = entityScriptingInterface->getEntityProperties(entityItemID); - if (EntityTypes::Web == properties.getType() && !properties.getLocked()) { + if (EntityTypes::Web == properties.getType() && !properties.getLocked() && properties.getVisible()) { auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(entityItemID); RenderableWebEntityItem* webEntity = dynamic_cast(entity.get()); if (webEntity) { webEntity->setProxyWindow(_window->windowHandle()); - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->pluginFocusOutEvent(); } _keyboardFocusedItem = entityItemID; @@ -1046,9 +1104,103 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : } }); + connect(this, &Application::aboutToQuit, [=]() { + _keyboardFocusedItem = UNKNOWN_ENTITY_ID; + if (_keyboardFocusHighlight) { + _keyboardFocusHighlight->setVisible(false); + } + }); + + // Add periodic checks to send user activity data + static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000; + static int SEND_STATS_INTERVAL_MS = 10000; + static int NEARBY_AVATAR_RADIUS_METERS = 10; + + // Periodically send fps as a user activity event + QTimer* sendStatsTimer = new QTimer(this); + sendStatsTimer->setInterval(SEND_STATS_INTERVAL_MS); + connect(sendStatsTimer, &QTimer::timeout, this, [this]() { + QJsonObject properties = {}; + MemoryInfo memInfo; + if (getMemoryInfo(memInfo)) { + properties["system_memory_total"] = static_cast(memInfo.totalMemoryBytes); + properties["system_memory_used"] = static_cast(memInfo.usedMemoryBytes); + properties["process_memory_used"] = static_cast(memInfo.processUsedMemoryBytes); + } + + auto displayPlugin = qApp->getActiveDisplayPlugin(); + + properties["fps"] = _frameCounter.rate(); + properties["present_rate"] = displayPlugin->presentRate(); + properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate(); + properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate(); + properties["sim_rate"] = getAverageSimsPerSecond(); + properties["avatar_sim_rate"] = getAvatarSimrate(); + + auto bandwidthRecorder = DependencyManager::get(); + properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond(); + properties["packet_rate_out"] = bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond(); + properties["kbps_in"] = bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond(); + properties["kbps_out"] = bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond(); + + auto nodeList = DependencyManager::get(); + SharedNodePointer entityServerNode = nodeList->soloNodeOfType(NodeType::EntityServer); + SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); + SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer); + SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer); + SharedNodePointer messagesMixerNode = nodeList->soloNodeOfType(NodeType::MessagesMixer); + properties["entity_ping"] = entityServerNode ? entityServerNode->getPingMs() : -1; + properties["audio_ping"] = audioMixerNode ? audioMixerNode->getPingMs() : -1; + properties["avatar_ping"] = avatarMixerNode ? avatarMixerNode->getPingMs() : -1; + properties["asset_ping"] = assetServerNode ? assetServerNode->getPingMs() : -1; + properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1; + + auto loadingRequests = ResourceCache::getLoadingRequests(); + properties["active_downloads"] = loadingRequests.size(); + properties["pending_downloads"] = ResourceCache::getPendingRequestCount(); + + properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false; + + UserActivityLogger::getInstance().logAction("stats", properties); + }); + sendStatsTimer->start(); + + + // Periodically check for count of nearby avatars + static int lastCountOfNearbyAvatars = -1; + QTimer* checkNearbyAvatarsTimer = new QTimer(this); + checkNearbyAvatarsTimer->setInterval(CHECK_NEARBY_AVATARS_INTERVAL_MS); + connect(checkNearbyAvatarsTimer, &QTimer::timeout, this, [this]() { + auto avatarManager = DependencyManager::get(); + int nearbyAvatars = avatarManager->numberOfAvatarsInRange(avatarManager->getMyAvatar()->getPosition(), + NEARBY_AVATAR_RADIUS_METERS) - 1; + if (nearbyAvatars != lastCountOfNearbyAvatars) { + lastCountOfNearbyAvatars = nearbyAvatars; + UserActivityLogger::getInstance().logAction("nearby_avatars", { { "count", nearbyAvatars } }); + } + }); + checkNearbyAvatarsTimer->start(); + + // Track user activity event when we receive a mute packet + auto onMutedByMixer = []() { + UserActivityLogger::getInstance().logAction("received_mute_packet"); + }; + connect(DependencyManager::get().data(), &AudioClient::mutedByMixer, this, onMutedByMixer); + + // Track when the address bar is opened + auto onAddressBarToggled = [this]() { + // Record time + UserActivityLogger::getInstance().logAction("opened_address_bar", { { "uptime_ms", _sessionRunTimer.elapsed() } }); + }; + connect(DependencyManager::get().data(), &DialogsManager::addressBarToggled, this, onAddressBarToggled); + // Make sure we don't time out during slow operations at startup updateHeartbeat(); + OctreeEditPacketSender* packetSender = entityScriptingInterface->getPacketSender(); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + entityPacketSender->setMyAvatar(getMyAvatar()); + connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0); @@ -1057,6 +1209,68 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : firstRun.set(false); } +void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) { + switch (static_cast(reasonCode)) { + case DomainHandler::ConnectionRefusedReason::ProtocolMismatch: + case DomainHandler::ConnectionRefusedReason::TooManyUsers: + case DomainHandler::ConnectionRefusedReason::Unknown: { + QString message = "Unable to connect to the location you are visiting.\n"; + message += reasonMessage; + OffscreenUi::warning("", message); + break; + } + default: + // nothing to do. + break; + } +} + +QString Application::getUserAgent() { + if (QThread::currentThread() != thread()) { + QString userAgent; + + QMetaObject::invokeMethod(this, "getUserAgent", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userAgent)); + + return userAgent; + } + + QString userAgent = "Mozilla/5.0 (HighFidelityInterface/" + BuildInfo::VERSION + "; " + + QSysInfo::productType() + " " + QSysInfo::productVersion() + ")"; + + auto formatPluginName = [](QString name) -> QString { return name.trimmed().replace(" ", "-"); }; + + // For each plugin, add to userAgent + auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); + for (auto& dp : displayPlugins) { + if (dp->isActive() && dp->isHmd()) { + userAgent += " " + formatPluginName(dp->getName()); + } + } + auto inputPlugins= PluginManager::getInstance()->getInputPlugins(); + for (auto& ip : inputPlugins) { + if (ip->isActive()) { + userAgent += " " + formatPluginName(ip->getName()); + } + } + // for codecs, we include all of them, even if not active + auto codecPlugins = PluginManager::getInstance()->getCodecPlugins(); + for (auto& cp : codecPlugins) { + userAgent += " " + formatPluginName(cp->getName()); + } + + return userAgent; +} + +void Application::toggleMenuUnderReticle() const { + // In HMD, if the menu is near the mouse but not under it, the reticle can be at a significantly + // different depth. When you focus on the menu, the cursor can appear to your crossed eyes as both + // on the menu and off. + // Even in 2D, it is arguable whether the user would want the menu to be to the side. + const float X_LEFT_SHIFT = 50.0; + auto offscreenUi = DependencyManager::get(); + auto reticlePosition = getApplicationCompositor().getReticlePosition(); + offscreenUi->toggleMenu(QPoint(reticlePosition.x - X_LEFT_SHIFT, reticlePosition.y)); +} void Application::checkChangeCursor() { QMutexLocker locker(&_changeCursorLock); @@ -1088,9 +1302,7 @@ void Application::aboutToQuit() { emit beforeAboutToQuit(); foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { - QString name = inputPlugin->getName(); - QAction* action = Menu::getInstance()->getActionForOption(name); - if (action->isChecked()) { + if (inputPlugin->isActive()) { inputPlugin->deactivate(); } } @@ -1325,7 +1537,6 @@ void Application::initializeUi() { // For some reason there is already an "Application" object in the QML context, // though I can't find it. Hence, "ApplicationInterface" - rootContext->setContextProperty("SnapshotUploader", new SnapshotUploader()); rootContext->setContextProperty("ApplicationInterface", this); rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance()); rootContext->setContextProperty("Controller", DependencyManager::get().data()); @@ -1354,7 +1565,7 @@ void Application::initializeUi() { rootContext->setContextProperty("Overlays", &_overlays); rootContext->setContextProperty("Window", DependencyManager::get().data()); - rootContext->setContextProperty("Menu", MenuScriptingInterface::getInstance()); + rootContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); rootContext->setContextProperty("Stats", Stats::getInstance()); rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); rootContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); @@ -1395,25 +1606,20 @@ void Application::initializeUi() { }); offscreenUi->resume(); connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){ - static qreal oldDevicePixelRatio = 0; - qreal devicePixelRatio = getActiveDisplayPlugin()->devicePixelRatio(); - if (devicePixelRatio != oldDevicePixelRatio) { - oldDevicePixelRatio = devicePixelRatio; - qDebug() << "Device pixel ratio changed, triggering GL resize"; - resizeGL(); - } + resizeGL(); }); // This will set up the input plugins UI _activeInputPlugins.clear(); foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { - QString name = inputPlugin->getName(); - if (name == KeyboardMouseDevice::NAME) { + if (KeyboardMouseDevice::NAME == inputPlugin->getName()) { _keyboardMouseDevice = std::dynamic_pointer_cast(inputPlugin); } + if (TouchscreenDevice::NAME == inputPlugin->getName()) { + _touchscreenDevice = std::dynamic_pointer_cast(inputPlugin); + } } _window->setMenuBar(new Menu()); - updateInputModes(); auto compositorHelper = DependencyManager::get(); connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, [=] { @@ -1447,7 +1653,13 @@ void Application::paintGL() { // FIXME not needed anymore? _offscreenContext->makeCurrent(); - displayPlugin->beginFrameRender(_frameCount); + // If a display plugin loses it's underlying support, it + // needs to be able to signal us to not use it + if (!displayPlugin->beginFrameRender(_frameCount)) { + _inPaint = false; + updateDisplayMode(); + return; + } // update the avatar with a fresh HMD pose getMyAvatar()->updateFromHMDSensorMatrix(getHMDSensorPose()); @@ -1477,17 +1689,16 @@ void Application::paintGL() { renderArgs._context->syncCache(); } - if (Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) { + auto inputs = AvatarInputs::getInstance(); + if (inputs->mirrorVisible()) { PerformanceTimer perfTimer("Mirror"); - auto primaryFbo = DependencyManager::get()->getPrimaryFramebuffer(); renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE; renderArgs._blitFramebuffer = DependencyManager::get()->getSelfieFramebuffer(); - auto inputs = AvatarInputs::getInstance(); _mirrorViewRect.moveTo(inputs->x(), inputs->y()); - renderRearViewMirror(&renderArgs, _mirrorViewRect); + renderRearViewMirror(&renderArgs, _mirrorViewRect, inputs->mirrorZoomed()); renderArgs._blitFramebuffer.reset(); renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE; @@ -1529,22 +1740,22 @@ void Application::paintGL() { if (isHMDMode()) { mat4 camMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); _myCamera.setPosition(extractTranslation(camMat)); - _myCamera.setRotation(glm::quat_cast(camMat)); + _myCamera.setOrientation(glm::quat_cast(camMat)); } else { _myCamera.setPosition(myAvatar->getDefaultEyePosition()); - _myCamera.setRotation(myAvatar->getHead()->getCameraOrientation()); + _myCamera.setOrientation(myAvatar->getHead()->getCameraOrientation()); } } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { if (isHMDMode()) { auto hmdWorldMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); - _myCamera.setRotation(glm::normalize(glm::quat_cast(hmdWorldMat))); + _myCamera.setOrientation(glm::normalize(glm::quat_cast(hmdWorldMat))); _myCamera.setPosition(extractTranslation(hmdWorldMat) + myAvatar->getOrientation() * boomOffset); } else { - _myCamera.setRotation(myAvatar->getHead()->getOrientation()); + _myCamera.setOrientation(myAvatar->getHead()->getOrientation()); if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) { _myCamera.setPosition(myAvatar->getDefaultEyePosition() - + _myCamera.getRotation() * boomOffset); + + _myCamera.getOrientation() * boomOffset); } else { _myCamera.setPosition(myAvatar->getDefaultEyePosition() + myAvatar->getOrientation() * boomOffset); @@ -1563,7 +1774,7 @@ void Application::paintGL() { glm::quat worldMirrorRotation = mirrorBodyOrientation * mirrorHmdRotation; - _myCamera.setRotation(worldMirrorRotation); + _myCamera.setOrientation(worldMirrorRotation); glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix()); // Mirror HMD lateral offsets @@ -1574,7 +1785,7 @@ void Application::paintGL() { + mirrorBodyOrientation * glm::vec3(0.0f, 0.0f, 1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror + mirrorBodyOrientation * hmdOffset); } else { - _myCamera.setRotation(myAvatar->getWorldAlignedOrientation() + _myCamera.setOrientation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); _myCamera.setPosition(myAvatar->getDefaultEyePosition() + glm::vec3(0, _raiseMirror * myAvatar->getUniformScale(), 0) @@ -1587,11 +1798,11 @@ void Application::paintGL() { if (cameraEntity != nullptr) { if (isHMDMode()) { glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix()); - _myCamera.setRotation(cameraEntity->getRotation() * hmdRotation); + _myCamera.setOrientation(cameraEntity->getRotation() * hmdRotation); glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix()); _myCamera.setPosition(cameraEntity->getPosition() + (hmdRotation * hmdOffset)); } else { - _myCamera.setRotation(cameraEntity->getRotation()); + _myCamera.setOrientation(cameraEntity->getRotation()); _myCamera.setPosition(cameraEntity->getPosition()); } } @@ -1772,13 +1983,29 @@ void Application::resizeGL() { static qreal lastDevicePixelRatio = 0; qreal devicePixelRatio = _window->devicePixelRatio(); if (offscreenUi->size() != fromGlm(uiSize) || devicePixelRatio != lastDevicePixelRatio) { - offscreenUi->resize(fromGlm(uiSize)); + qDebug() << "Device pixel ratio changed, triggering resize"; + offscreenUi->resize(fromGlm(uiSize), true); _offscreenContext->makeCurrent(); lastDevicePixelRatio = devicePixelRatio; } } +bool Application::importJSONFromURL(const QString& urlString) { + // we only load files that terminate in just .json (not .svo.json and not .ava.json) + // if they come from the High Fidelity Marketplace Assets CDN + + QUrl jsonURL { urlString }; + + if (jsonURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) { + emit svoImportRequested(urlString); + return true; + } else { + return false; + } +} + bool Application::importSVOFromURL(const QString& urlString) { + emit svoImportRequested(urlString); return true; } @@ -1789,25 +2016,40 @@ bool Application::event(QEvent* event) { return false; } - static bool justPresented = false; + // Presentation/painting logic + // TODO: Decouple presentation and painting loops + static bool isPaintingThrottled = false; if ((int)event->type() == (int)Present) { - if (justPresented) { - justPresented = false; - - // If presentation is hogging the main thread, repost as low priority to avoid hanging the GUI. + if (isPaintingThrottled) { + // If painting (triggered by presentation) is hogging the main thread, + // repost as low priority to avoid hanging the GUI. // This has the effect of allowing presentation to exceed the paint budget by X times and - // only dropping every (1/X) frames, instead of every ceil(X) frames. + // only dropping every (1/X) frames, instead of every ceil(X) frames // (e.g. at a 60FPS target, painting for 17us would fall to 58.82FPS instead of 30FPS). removePostedEvents(this, Present); postEvent(this, new QEvent(static_cast(Present)), Qt::LowEventPriority); + isPaintingThrottled = false; return true; } - idle(); + float nsecsElapsed = (float)_lastTimeUpdated.nsecsElapsed(); + if (shouldPaint(nsecsElapsed)) { + _lastTimeUpdated.start(); + idle(nsecsElapsed); + postEvent(this, new QEvent(static_cast(Paint)), Qt::HighEventPriority); + } + isPaintingThrottled = true; + return true; } else if ((int)event->type() == (int)Paint) { - justPresented = true; + // NOTE: This must be updated as close to painting as possible, + // or AvatarInputs will mysteriously move to the bottom-right + AvatarInputs::getInstance()->update(); + paintGL(); + + isPaintingThrottled = false; + return true; } @@ -1871,6 +2113,9 @@ bool Application::event(QEvent* event) { case QEvent::TouchUpdate: touchUpdateEvent(static_cast(event)); return true; + case QEvent::Gesture: + touchGestureEvent((QGestureEvent*)event); + return true; case QEvent::Wheel: wheelEvent(static_cast(event)); return true; @@ -1940,7 +2185,7 @@ void Application::keyPressEvent(QKeyEvent* event) { } if (hasFocus()) { - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyPressEvent(event); } @@ -1952,9 +2197,9 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Return: if (isOption) { if (_window->isFullScreen()) { - _pluginContainer->unsetFullscreen(); + unsetFullscreen(); } else { - _pluginContainer->setFullscreen(nullptr); + setFullscreen(nullptr); } } else { Menu::getInstance()->triggerOption(MenuOption::AddressBar); @@ -1986,7 +2231,8 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_X: if (isShifted && isMeta) { auto offscreenUi = DependencyManager::get(); - offscreenUi->getRootContext()->engine()->clearComponentCache(); + offscreenUi->togglePinned(); + //offscreenUi->getRootContext()->engine()->clearComponentCache(); //OffscreenUi::information("Debugging", "Component cache cleared"); // placeholder for dialogs being converted to QML. } @@ -2260,9 +2506,7 @@ void Application::keyPressEvent(QKeyEvent* event) { void Application::keyReleaseEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) { - auto offscreenUi = DependencyManager::get(); - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); + toggleMenuUnderReticle(); } _keysPressed.remove(event->key()); @@ -2274,7 +2518,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } @@ -2306,9 +2550,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) { void Application::focusOutEvent(QFocusEvent* event) { auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); foreach(auto inputPlugin, inputPlugins) { - QString name = inputPlugin->getName(); - QAction* action = Menu::getInstance()->getActionForOption(name); - if (action && action->isChecked()) { + if (inputPlugin->isActive()) { inputPlugin->pluginFocusOutEvent(); } } @@ -2393,7 +2635,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mouseMoveEvent(event); } @@ -2430,7 +2672,7 @@ void Application::mousePressEvent(QMouseEvent* event) { if (hasFocus()) { - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mousePressEvent(event); } @@ -2475,7 +2717,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { } if (hasFocus()) { - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mouseReleaseEvent(event); } @@ -2502,9 +2744,12 @@ void Application::touchUpdateEvent(QTouchEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchUpdateEvent(event); } + if (_touchscreenDevice && _touchscreenDevice->isActive()) { + _touchscreenDevice->touchUpdateEvent(event); + } } void Application::touchBeginEvent(QTouchEvent* event) { @@ -2520,9 +2765,12 @@ void Application::touchBeginEvent(QTouchEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchBeginEvent(event); } + if (_touchscreenDevice && _touchscreenDevice->isActive()) { + _touchscreenDevice->touchBeginEvent(event); + } } @@ -2537,13 +2785,22 @@ void Application::touchEndEvent(QTouchEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchEndEvent(event); } + if (_touchscreenDevice && _touchscreenDevice->isActive()) { + _touchscreenDevice->touchEndEvent(event); + } // put any application specific touch behavior below here.. } +void Application::touchGestureEvent(QGestureEvent* event) { + if (_touchscreenDevice && _touchscreenDevice->isActive()) { + _touchscreenDevice->touchGestureEvent(event); + } +} + void Application::wheelEvent(QWheelEvent* event) const { _altPressed = false; _controllerScriptingInterface->emitWheelEvent(event); // send events to any registered scripts @@ -2553,7 +2810,7 @@ void Application::wheelEvent(QWheelEvent* event) const { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->wheelEvent(event); } } @@ -2590,10 +2847,9 @@ bool Application::acceptSnapshot(const QString& urlString) { static uint32_t _renderedFrameIndex { INVALID_FRAME }; -void Application::idle() { - // idle is called on a queued connection, so make sure we should be here. - if (_inPaint || _aboutToQuit) { - return; +bool Application::shouldPaint(float nsecsElapsed) { + if (_aboutToQuit) { + return false; } auto displayPlugin = getActiveDisplayPlugin(); @@ -2612,18 +2868,20 @@ void Application::idle() { } #endif - float msecondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_USEC / USECS_PER_MSEC; + float msecondsSinceLastUpdate = nsecsElapsed / NSECS_PER_USEC / USECS_PER_MSEC; // Throttle if requested if (displayPlugin->isThrottled() && (msecondsSinceLastUpdate < THROTTLED_SIM_FRAME_PERIOD_MS)) { - return; + return false; } // Sync up the _renderedFrameIndex _renderedFrameIndex = displayPlugin->presentCount(); - // Request a paint ASAP - postEvent(this, new QEvent(static_cast(Paint)), Qt::HighEventPriority + 1); + return true; +} + +void Application::idle(float nsecsElapsed) { // Update the deadlock watchdog updateHeartbeat(); @@ -2636,15 +2894,11 @@ void Application::idle() { if (firstIdle) { firstIdle = false; connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop); - _overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays)); - } else { - // FIXME: AvatarInputs are positioned incorrectly if instantiated before the first paint - AvatarInputs::getInstance()->update(); } PROFILE_RANGE(__FUNCTION__); - float secondsSinceLastUpdate = msecondsSinceLastUpdate / MSECS_PER_SECOND; + float secondsSinceLastUpdate = nsecsElapsed / NSECS_PER_MSEC / MSECS_PER_SECOND; // If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus. if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) { @@ -2654,11 +2908,6 @@ void Application::idle() { _keyboardDeviceHasFocus = true; } - - - // We're going to execute idle processing, so restart the last idle timer - _lastTimeUpdated.start(); - checkChangeCursor(); Stats::getInstance()->updateStats(); @@ -2693,9 +2942,7 @@ void Application::idle() { getActiveDisplayPlugin()->idle(); auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); foreach(auto inputPlugin, inputPlugins) { - QString name = inputPlugin->getName(); - QAction* action = Menu::getInstance()->getActionForOption(name); - if (action && action->isChecked()) { + if (inputPlugin->isActive()) { inputPlugin->idle(); } } @@ -2847,7 +3094,7 @@ bool Application::exportEntities(const QString& filename, const QVectorwriteToJSONFile(filename.toLocal8Bit().constData()); + success = exportTree->writeToJSONFile(filename.toLocal8Bit().constData()); // restore the main window's active state _window->activateWindow(); @@ -2856,17 +3103,20 @@ bool Application::exportEntities(const QString& filename, const QVector entities; QVector ids; auto entityTree = getEntities()->getTree(); entityTree->withReadLock([&] { - entityTree->findEntities(AACube(offset, scale), entities); + entityTree->findEntities(boundingCube, entities); foreach(EntityItemPointer entity, entities) { ids << entity->getEntityItemID(); } }); - return exportEntities(filename, ids, &offset); + return exportEntities(filename, ids, ¢er); } void Application::loadSettings() { @@ -2879,13 +3129,33 @@ void Application::loadSettings() { //DependencyManager::get()->setAutomaticLODAdjust(false); Menu::getInstance()->loadSettings(); + // If there is a preferred plugin, we probably messed it up with the menu settings, so fix it. + auto pluginManager = PluginManager::getInstance(); + auto plugins = pluginManager->getPreferredDisplayPlugins(); + for (auto plugin : plugins) { + auto menu = Menu::getInstance(); + if (auto action = menu->getActionForOption(plugin->getName())) { + action->setChecked(true); + action->trigger(); + // Find and activated highest priority plugin, bail for the rest + break; + } + } + + auto inputs = pluginManager->getInputPlugins(); + for (auto plugin : inputs) { + if (!plugin->isActive()) { + plugin->activate(); + } + } + getMyAvatar()->loadData(); _settingsLoaded = true; } void Application::saveSettings() const { - sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SEC); + sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SECOND); DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); @@ -3104,9 +3374,9 @@ void Application::updateMyAvatarLookAtPosition() { if (isLookingAtSomeone) { deflection *= GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT; } - lookAtSpot = origin + _myCamera.getRotation() * glm::quat(glm::radians(glm::vec3( + lookAtSpot = origin + _myCamera.getOrientation() * glm::quat(glm::radians(glm::vec3( eyePitch * deflection, eyeYaw * deflection, 0.0f))) * - glm::inverse(_myCamera.getRotation()) * (lookAtSpot - origin); + glm::inverse(_myCamera.getOrientation()) * (lookAtSpot - origin); } } @@ -3126,13 +3396,13 @@ void Application::updateThreads(float deltaTime) { } void Application::toggleOverlays() { - auto newOverlaysVisible = !_overlayConductor.getEnabled(); - Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, newOverlaysVisible); - _overlayConductor.setEnabled(newOverlaysVisible); + auto menu = Menu::getInstance(); + menu->setIsOptionChecked(MenuOption::Overlays, menu->isOptionChecked(MenuOption::Overlays)); } void Application::setOverlaysVisible(bool visible) { - _overlayConductor.setEnabled(visible); + auto menu = Menu::getInstance(); + menu->setIsOptionChecked(MenuOption::Overlays, true); } void Application::cycleCamera() { @@ -3336,22 +3606,18 @@ void Application::update(float deltaTime) { }; InputPluginPointer keyboardMousePlugin; - bool jointsCaptured = false; for (auto inputPlugin : PluginManager::getInstance()->getInputPlugins()) { if (inputPlugin->getName() == KeyboardMouseDevice::NAME) { keyboardMousePlugin = inputPlugin; } else if (inputPlugin->isActive()) { - inputPlugin->pluginUpdate(deltaTime, calibrationData, jointsCaptured); - if (inputPlugin->isJointController()) { - jointsCaptured = true; - } + inputPlugin->pluginUpdate(deltaTime, calibrationData); } } userInputMapper->update(deltaTime); if (keyboardMousePlugin && keyboardMousePlugin->isActive()) { - keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData, jointsCaptured); + keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData); } _controllerScriptingInterface->updateInputControllers(); @@ -3557,10 +3823,6 @@ void Application::update(float deltaTime) { int Application::sendNackPackets() { - if (Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) { - return 0; - } - // iterates through all nodes in NodeList auto nodeList = DependencyManager::get(); @@ -3655,11 +3917,11 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node } else { const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - unsigned char* rootCode = map.getRootOctalCode(); + auto rootCode = map.getRootOctalCode(); if (rootCode) { VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE, rootDetails.y * TREE_SCALE, rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE), @@ -3719,11 +3981,11 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node } else { const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - unsigned char* rootCode = map.getRootOctalCode(); + auto rootCode = map.getRootOctalCode(); if (rootCode) { VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE, rootDetails.y * TREE_SCALE, rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE), @@ -3830,7 +4092,7 @@ void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) { // Set the viewFrustum up with the correct position and orientation of the camera viewFrustum.setPosition(camera.getPosition()); - viewFrustum.setOrientation(camera.getRotation()); + viewFrustum.setOrientation(camera.getOrientation()); // Ask the ViewFrustum class to calculate our corners viewFrustum.calculate(); @@ -4068,7 +4330,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se activeRenderingThread = nullptr; } -void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& region) { +void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed) { auto originalViewport = renderArgs->_viewport; // Grab current viewport to reset it at the end @@ -4078,7 +4340,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi auto myAvatar = getMyAvatar(); // bool eyeRelativeCamera = false; - if (!AvatarInputs::getInstance()->mirrorZoomed()) { + if (!isZoomed) { _mirrorCamera.setPosition(myAvatar->getChestPosition() + myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_BODY_DISTANCE * myAvatar->getScale()); @@ -4103,7 +4365,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * myAvatar->getScale()); } _mirrorCamera.setProjection(glm::perspective(glm::radians(fov), aspect, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); - _mirrorCamera.setRotation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); + _mirrorCamera.setOrientation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); // set the bounds of rear mirror view @@ -4125,6 +4387,7 @@ void Application::resetSensors(bool andReload) { DependencyManager::get()->reset(); DependencyManager::get()->reset(); getActiveDisplayPlugin()->resetSensors(); + _overlayConductor.centerUI(); getMyAvatar()->reset(andReload); QMetaObject::invokeMethod(DependencyManager::get().data(), "reset", Qt::QueuedConnection); } @@ -4135,7 +4398,7 @@ void Application::updateWindowTitle() const { auto nodeList = DependencyManager::get(); QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) "; - QString username = AccountManager::getInstance().getAccountInfo().getUsername(); + QString username = DependencyManager::get()->getAccountInfo().getUsername(); QString currentPlaceName = DependencyManager::get()->getHost(); if (currentPlaceName.isEmpty()) { @@ -4162,6 +4425,7 @@ void Application::clearDomainOctreeDetails() { qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); + getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities // reset our node to stats and node to jurisdiction maps... since these must be changing... _entityServerJurisdictions.withWriteLock([&] { @@ -4209,7 +4473,7 @@ void Application::nodeActivated(SharedNodePointer node) { if (assetDialog) { auto nodeList = DependencyManager::get(); - if (nodeList->getThisNodeCanRez()) { + if (nodeList->getThisNodeCanWriteAssets()) { // call reload on the shown asset browser dialog to get the mappings (if permissions allow) QMetaObject::invokeMethod(assetDialog, "reload"); } else { @@ -4229,6 +4493,9 @@ void Application::nodeActivated(SharedNodePointer node) { } } + if (node->getType() == NodeType::AudioMixer) { + DependencyManager::get()->negotiateAudioFormat(); + } } void Application::nodeKilled(SharedNodePointer node) { @@ -4250,9 +4517,9 @@ void Application::nodeKilled(SharedNodePointer node) { return; } - unsigned char* rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode(); + auto rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode(); VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); qCDebug(interfaceapp, "model server going away...... v[%f, %f, %f, %f]", (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); @@ -4371,27 +4638,33 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer serverType = "Entity"; } + bool found = false; + jurisdiction->withReadLock([&] { if (jurisdiction->find(nodeUUID) != jurisdiction->end()) { + found = true; return; } VoxelPositionSize rootDetails; - voxelDetailsForCode(octreeStats.getJurisdictionRoot(), rootDetails); + voxelDetailsForCode(octreeStats.getJurisdictionRoot().get(), rootDetails); qCDebug(interfaceapp, "stats from new %s server... [%f, %f, %f, %f]", qPrintable(serverType), (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); }); - // store jurisdiction details for later use - // This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it - // but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the - // details from the OctreeSceneStats to construct the JurisdictionMap - JurisdictionMap jurisdictionMap; - jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes()); - jurisdiction->withWriteLock([&] { - (*jurisdiction)[nodeUUID] = jurisdictionMap; - }); + + if (!found) { + // store jurisdiction details for later use + // This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it + // but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the + // details from the OctreeSceneStats to construct the JurisdictionMap + JurisdictionMap jurisdictionMap; + jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes()); + jurisdiction->withWriteLock([&] { + (*jurisdiction)[nodeUUID] = jurisdictionMap; + }); + } }); return statsMessageLength; @@ -4433,7 +4706,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); + scriptEngine->registerGlobalObject("OffscreenFlags", DependencyManager::get()->getFlags()); scriptEngine->registerGlobalObject("Desktop", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Window", DependencyManager::get().data()); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, @@ -4482,6 +4757,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface()); + + scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); } bool Application::canAcceptURL(const QString& urlString) const { @@ -4587,7 +4865,17 @@ bool Application::askToSetAvatarUrl(const QString& url) { bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { QMessageBox::StandardButton reply; - QString message = "Would you like to run this script:\n" + scriptFilenameOrURL; + + QString shortName = scriptFilenameOrURL; + + QUrl scriptURL { scriptFilenameOrURL }; + + if (scriptURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) { + shortName = shortName.mid(shortName.lastIndexOf('/') + 1); + } + + QString message = "Would you like to run this script:\n" + shortName; + reply = OffscreenUi::question(getWindow(), "Run Script", message, QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { @@ -4699,7 +4987,7 @@ void Application::toggleRunningScriptsWidget() const { } void Application::toggleAssetServerWidget(QString filePath) { - if (!DependencyManager::get()->getThisNodeCanRez()) { + if (!DependencyManager::get()->getThisNodeCanWriteAssets()) { return; } @@ -4773,17 +5061,9 @@ void Application::takeSnapshot() { player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath())); player->play(); - QString fileName = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot()); + QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot()); - AccountManager& accountManager = AccountManager::getInstance(); - if (!accountManager.isLoggedIn()) { - return; - } - - DependencyManager::get()->load("hifi/dialogs/SnapshotShareDialog.qml", [=](QQmlContext*, QObject* dialog) { - dialog->setProperty("source", QUrl::fromLocalFile(fileName)); - connect(dialog, SIGNAL(uploadSnapshot(const QString& snapshot)), this, SLOT(uploadSnapshot(const QString& snapshot))); - }); + emit DependencyManager::get()->snapshotTaken(path); } float Application::getRenderResolutionScale() const { @@ -4849,7 +5129,34 @@ void Application::postLambdaEvent(std::function f) { } } -void Application::initPlugins() { +void Application::initPlugins(const QStringList& arguments) { + QCommandLineOption display("display", "Preferred displays", "displays"); + QCommandLineOption disableDisplays("disable-displays", "Displays to disable", "displays"); + QCommandLineOption disableInputs("disable-inputs", "Inputs to disable", "inputs"); + + QCommandLineParser parser; + parser.addOption(display); + parser.addOption(disableDisplays); + parser.addOption(disableInputs); + parser.parse(arguments); + + if (parser.isSet(display)) { + auto preferredDisplays = parser.value(display).split(',', QString::SkipEmptyParts); + qInfo() << "Setting prefered display plugins:" << preferredDisplays; + PluginManager::getInstance()->setPreferredDisplayPlugins(preferredDisplays); + } + + if (parser.isSet(disableDisplays)) { + auto disabledDisplays = parser.value(disableDisplays).split(',', QString::SkipEmptyParts); + qInfo() << "Disabling following display plugins:" << disabledDisplays; + PluginManager::getInstance()->disableDisplays(disabledDisplays); + } + + if (parser.isSet(disableInputs)) { + auto disabledInputs = parser.value(disableInputs).split(',', QString::SkipEmptyParts); + qInfo() << "Disabling following input plugins:" << disabledInputs; + PluginManager::getInstance()->disableInputs(disabledInputs); + } } void Application::shutdownPlugins() { @@ -5024,6 +5331,7 @@ void Application::updateDisplayMode() { foreach(auto displayPlugin, standard) { addDisplayPluginToMenu(displayPlugin, first); + auto displayPluginName = displayPlugin->getName(); QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) { resizeGL(); }); @@ -5042,6 +5350,10 @@ void Application::updateDisplayMode() { foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { QString name = displayPlugin->getName(); QAction* action = menu->getActionForOption(name); + // Menu might have been removed if the display plugin lost + if (!action) { + continue; + } if (action->isChecked()) { newDisplayPlugin = displayPlugin; break; @@ -5052,6 +5364,11 @@ void Application::updateDisplayMode() { return; } + UserActivityLogger::getInstance().logAction("changed_display_mode", { + { "previous_display_mode", _displayPlugin ? _displayPlugin->getName() : "" }, + { "display_mode", newDisplayPlugin ? newDisplayPlugin->getName() : "" } + }); + auto offscreenUi = DependencyManager::get(); // Make the switch atomic from the perspective of other threads @@ -5098,7 +5415,6 @@ void Application::updateDisplayMode() { _displayPlugin = newDisplayPlugin; } - emit activeDisplayPluginChanged(); // reset the avatar, to set head and hand palms back to a reasonable default pose. @@ -5107,81 +5423,6 @@ void Application::updateDisplayMode() { Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } -static void addInputPluginToMenu(InputPluginPointer inputPlugin, bool active = false) { - auto menu = Menu::getInstance(); - QString name = inputPlugin->getName(); - Q_ASSERT(!menu->menuItemExists(MenuOption::InputMenu, name)); - - static QActionGroup* inputPluginGroup = nullptr; - if (!inputPluginGroup) { - inputPluginGroup = new QActionGroup(menu); - } - auto parent = menu->getMenu(MenuOption::InputMenu); - auto action = menu->addCheckableActionToQMenuAndActionHash(parent, - name, 0, active, qApp, - SLOT(updateInputModes())); - inputPluginGroup->addAction(action); - inputPluginGroup->setExclusive(false); - Q_ASSERT(menu->menuItemExists(MenuOption::InputMenu, name)); -} - - -void Application::updateInputModes() { - auto menu = Menu::getInstance(); - auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); - static std::once_flag once; - std::call_once(once, [&] { - bool first = true; - foreach(auto inputPlugin, inputPlugins) { - addInputPluginToMenu(inputPlugin, first); - first = false; - } - }); - auto offscreenUi = DependencyManager::get(); - - InputPluginList newInputPlugins; - InputPluginList removedInputPlugins; - foreach(auto inputPlugin, inputPlugins) { - QString name = inputPlugin->getName(); - QAction* action = menu->getActionForOption(name); - - auto it = std::find(std::begin(_activeInputPlugins), std::end(_activeInputPlugins), inputPlugin); - if (action->isChecked() && it == std::end(_activeInputPlugins)) { - _activeInputPlugins.push_back(inputPlugin); - newInputPlugins.push_back(inputPlugin); - } else if (!action->isChecked() && it != std::end(_activeInputPlugins)) { - _activeInputPlugins.erase(it); - removedInputPlugins.push_back(inputPlugin); - } - } - - // A plugin was checked - if (newInputPlugins.size() > 0) { - foreach(auto newInputPlugin, newInputPlugins) { - newInputPlugin->activate(); - //newInputPlugin->installEventFilter(qApp); - //newInputPlugin->installEventFilter(offscreenUi.data()); - } - } - if (removedInputPlugins.size() > 0) { // A plugin was unchecked - foreach(auto removedInputPlugin, removedInputPlugins) { - removedInputPlugin->deactivate(); - //removedInputPlugin->removeEventFilter(qApp); - //removedInputPlugin->removeEventFilter(offscreenUi.data()); - } - } - - //if (newInputPlugins.size() > 0 || removedInputPlugins.size() > 0) { - // if (!_currentInputPluginActions.isEmpty()) { - // auto menu = Menu::getInstance(); - // foreach(auto itemInfo, _currentInputPluginActions) { - // menu->removeMenuItem(itemInfo.first, itemInfo.second); - // } - // _currentInputPluginActions.clear(); - // } - //} -} - mat4 Application::getEyeProjection(int eye) const { QMutexLocker viewLocker(&_viewMutex); if (isHMDMode()) { @@ -5251,11 +5492,55 @@ void Application::readArgumentsFromLocalSocket() const { } void Application::showDesktop() { - if (!_overlayConductor.getEnabled()) { - _overlayConductor.setEnabled(true); - } + Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, true); } CompositorHelper& Application::getApplicationCompositor() const { return *DependencyManager::get(); } + + +// virtual functions required for PluginContainer +ui::Menu* Application::getPrimaryMenu() { + auto appMenu = _window->menuBar(); + auto uiMenu = dynamic_cast(appMenu); + return uiMenu; +} + +void Application::showDisplayPluginsTools(bool show) { + DependencyManager::get()->hmdTools(show); +} + +GLWidget* Application::getPrimaryWidget() { + return _glWidget; +} + +MainWindow* Application::getPrimaryWindow() { + return getWindow(); +} + +QOpenGLContext* Application::getPrimaryContext() { + return _glWidget->context()->contextHandle(); +} + +bool Application::makeRenderingContextCurrent() { + return _offscreenContext->makeCurrent(); +} + +void Application::releaseSceneTexture(const gpu::TexturePointer& texture) { + Q_ASSERT(QThread::currentThread() == thread()); + auto& framebufferMap = _lockedFramebufferMap; + Q_ASSERT(framebufferMap.contains(texture)); + auto framebufferPointer = framebufferMap[texture]; + framebufferMap.remove(texture); + auto framebufferCache = DependencyManager::get(); + framebufferCache->releaseFramebuffer(framebufferPointer); +} + +void Application::releaseOverlayTexture(const gpu::TexturePointer& texture) { + _applicationOverlay.releaseOverlay(texture); +} + +bool Application::isForeground() const { + return _isForeground && !_window->isMinimized(); +} diff --git a/interface/src/Application.h b/interface/src/Application.h index 558190c8d1..a1e3e2f930 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -29,11 +29,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -86,14 +88,32 @@ class Application; #endif #define qApp (static_cast(QCoreApplication::instance())) -class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface, public AbstractUriHandler { +class Application : public QApplication, + public AbstractViewStateInterface, + public AbstractScriptingServicesInterface, + public AbstractUriHandler, + public PluginContainer { Q_OBJECT // TODO? Get rid of those friend class OctreePacketProcessor; - friend class PluginContainerProxy; public: + + // virtual functions required for PluginContainer + virtual ui::Menu* getPrimaryMenu() override; + virtual void requestReset() override { resetSensors(true); } + virtual void showDisplayPluginsTools(bool show) override; + virtual GLWidget* getPrimaryWidget() override; + virtual MainWindow* getPrimaryWindow() override; + virtual QOpenGLContext* getPrimaryContext() override; + virtual bool makeRenderingContextCurrent() override; + virtual void releaseSceneTexture(const gpu::TexturePointer& texture) override; + virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) override; + virtual bool isForeground() const override; + + virtual DisplayPluginPointer getActiveDisplayPlugin() const override; + enum Event { Present = DisplayPlugin::Present, Paint = Present + 1, @@ -101,7 +121,7 @@ public: }; // FIXME? Empty methods, do we still need them? - static void initPlugins(); + static void initPlugins(const QStringList& arguments); static void shutdownPlugins(); Application(int& argc, char** argv, QElapsedTimer& startup_time); @@ -112,6 +132,9 @@ public: QString getPreviousScriptLocation(); void setPreviousScriptLocation(const QString& previousScriptLocation); + // Return an HTTP User-Agent string with OS and device information. + Q_INVOKABLE QString getUserAgent(); + void initializeGL(); void initializeUi(); void paintGL(); @@ -160,7 +183,6 @@ public: Overlays& getOverlays() { return _overlays; } - bool isForeground() const { return _isForeground; } size_t getFrameCount() const { return _frameCount; } float getFps() const { return _frameCounter.rate(); } @@ -182,8 +204,6 @@ public: void setActiveDisplayPlugin(const QString& pluginName); - DisplayPluginPointer getActiveDisplayPlugin() const; - FileLogger* getLogger() const { return _logger; } glm::vec2 getViewportDimensions() const; @@ -192,6 +212,8 @@ public: float getRenderResolutionScale() const; + qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); } + bool isAboutToQuit() const { return _aboutToQuit; } // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display @@ -258,6 +280,10 @@ public slots: void resetSensors(bool andReload = false); void setActiveFaceTracker() const; +#if (PR_BUILD || DEV_BUILD) + void sendWrongProtocolVersionsSignature(bool checked) { ::sendWrongProtocolVersionsSignature(checked); } +#endif + #ifdef HAVE_IVIEWHMD void setActiveEyeTracker(); void calibrateEyeTracker1Point(); @@ -311,6 +337,7 @@ private slots: bool displayAvatarAttachmentConfirmationDialog(const QString& name) const; void setSessionUUID(const QUuid& sessionUUID) const; + void domainChanged(const QString& domainHostname); void updateWindowTitle() const; void nodeAdded(SharedNodePointer node) const; @@ -318,7 +345,7 @@ private slots: void nodeKilled(SharedNodePointer node); static void packetSent(quint64 length); void updateDisplayMode(); - void updateInputModes(); + void domainConnectionRefused(const QString& reasonMessage, int reason); private: static void initDisplay(); @@ -326,7 +353,8 @@ private: void cleanupBeforeQuit(); - void idle(); + bool shouldPaint(float nsecsElapsed); + void idle(float nsecsElapsed); void update(float deltaTime); // Various helper functions called during update() @@ -339,7 +367,7 @@ private: glm::vec3 getSunDirection() const; - void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region); + void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed); int sendNackPackets(); @@ -353,6 +381,7 @@ private: void displaySide(RenderArgs* renderArgs, Camera& whichCamera, bool selfAvatarOnly = false); + bool importJSONFromURL(const QString& urlString); bool importSVOFromURL(const QString& urlString); bool nearbyEntitiesAreReadyForPhysics(); @@ -375,12 +404,14 @@ private: void touchBeginEvent(QTouchEvent* event); void touchEndEvent(QTouchEvent* event); void touchUpdateEvent(QTouchEvent* event); + void touchGestureEvent(QGestureEvent* event); void wheelEvent(QWheelEvent* event) const; void dropEvent(QDropEvent* event); static void dragEnterEvent(QDragEnterEvent* event); void maybeToggleMenuVisible(QMouseEvent* event) const; + void toggleMenuUnderReticle() const; MainWindow* _window; QElapsedTimer& _sessionRunTimer; @@ -426,6 +457,7 @@ private: std::shared_ptr _applicationStateDevice; // Default ApplicationDevice reflecting the state of different properties of the session std::shared_ptr _keyboardMouseDevice; // Default input device, the good old keyboard mouse and maybe touchpad + std::shared_ptr _touchscreenDevice; // the good old touchscreen SimpleMovingAverage _avatarSimsPerSecond {10}; int _avatarSimsPerSecondReport {0}; quint64 _lastAvatarSimsPerSecondUpdate {0}; diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 53a3500bff..227bdadb97 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -62,14 +62,14 @@ void Camera::update(float deltaTime) { } void Camera::recompose() { - mat4 orientation = glm::mat4_cast(_rotation); + mat4 orientation = glm::mat4_cast(_orientation); mat4 translation = glm::translate(mat4(), _position); _transform = translation * orientation; } void Camera::decompose() { _position = vec3(_transform[3]); - _rotation = glm::quat_cast(_transform); + _orientation = glm::quat_cast(_transform); } void Camera::setTransform(const glm::mat4& transform) { @@ -85,8 +85,8 @@ void Camera::setPosition(const glm::vec3& position) { } } -void Camera::setRotation(const glm::quat& rotation) { - _rotation = rotation; +void Camera::setOrientation(const glm::quat& orientation) { + _orientation = orientation; recompose(); if (_isKeepLookingAt) { lookAt(_lookingAt); @@ -154,9 +154,9 @@ QString Camera::getModeString() const { void Camera::lookAt(const glm::vec3& lookAt) { glm::vec3 up = IDENTITY_UP; glm::mat4 lookAtMatrix = glm::lookAt(_position, lookAt, up); - glm::quat rotation = glm::quat_cast(lookAtMatrix); - rotation.w = -rotation.w; // Rosedale approved - _rotation = rotation; + glm::quat orientation = glm::quat_cast(lookAtMatrix); + orientation.w = -orientation.w; // Rosedale approved + _orientation = orientation; } void Camera::keepLookingAt(const glm::vec3& point) { @@ -171,7 +171,7 @@ void Camera::loadViewFrustum(ViewFrustum& frustum) const { // Set the viewFrustum up with the correct position and orientation of the camera frustum.setPosition(getPosition()); - frustum.setOrientation(getRotation()); + frustum.setOrientation(getOrientation()); // Ask the ViewFrustum class to calculate our corners frustum.calculate(); diff --git a/interface/src/Camera.h b/interface/src/Camera.h index 017bd742a4..46cad2efc8 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -29,6 +29,10 @@ enum CameraMode }; Q_DECLARE_METATYPE(CameraMode); + +#if defined(__GNUC__) && !defined(__clang__) +__attribute__((unused)) +#endif static int cameraModeId = qRegisterMetaType(); class Camera : public QObject { @@ -41,7 +45,7 @@ class Camera : public QObject { public: Camera(); - void initialize(); // instantly put the camera at the ideal position and rotation. + void initialize(); // instantly put the camera at the ideal position and orientation. void update( float deltaTime ); @@ -53,25 +57,22 @@ public: EntityItemPointer getCameraEntityPointer() const { return _cameraEntity; } -public slots: - QString getModeString() const; - void setModeString(const QString& mode); - - glm::quat getRotation() const { return _rotation; } - void setRotation(const glm::quat& rotation); - - glm::vec3 getPosition() const { return _position; } - void setPosition(const glm::vec3& position); - - glm::quat getOrientation() const { return getRotation(); } - void setOrientation(const glm::quat& orientation) { setRotation(orientation); } - const glm::mat4& getTransform() const { return _transform; } void setTransform(const glm::mat4& transform); const glm::mat4& getProjection() const { return _projection; } void setProjection(const glm::mat4& projection); +public slots: + QString getModeString() const; + void setModeString(const QString& mode); + + glm::vec3 getPosition() const { return _position; } + void setPosition(const glm::vec3& position); + + glm::quat getOrientation() const { return _orientation; } + void setOrientation(const glm::quat& orientation); + QUuid getCameraEntity() const; void setCameraEntity(QUuid entityID); @@ -101,7 +102,7 @@ private: // derived glm::vec3 _position; - glm::quat _rotation; + glm::quat _orientation; bool _isKeepLookingAt{ false }; glm::vec3 _lookingAt; EntityItemPointer _cameraEntity; diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp index 8de6766c7a..3c5f03bef3 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashHandler.cpp @@ -27,7 +27,7 @@ static const QString RUNNING_MARKER_FILENAME = "Interface.running"; -bool CrashHandler::checkForResetSettings() { +bool CrashHandler::checkForResetSettings(bool suppressPrompt) { QSettings::setDefaultFormat(QSettings::IniFormat); QSettings settings; settings.beginGroup("Developer"); @@ -42,6 +42,10 @@ bool CrashHandler::checkForResetSettings() { QFile runningMarkerFile(runningMarkerFilePath()); bool wasLikelyCrash = runningMarkerFile.exists(); + if (suppressPrompt) { + return wasLikelyCrash; + } + if (wasLikelyCrash || askToResetSettings) { if (displaySettingsResetOnCrash || askToResetSettings) { Action action = promptUserForAction(wasLikelyCrash); diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index 566b780d61..a65fed677d 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -17,7 +17,7 @@ class CrashHandler { public: - static bool checkForResetSettings(); + static bool checkForResetSettings(bool suppressPrompt = false); static void writeRunningMarkerFiler(); static void deleteRunningMarkerFile(); diff --git a/interface/src/CrashReporter.cpp b/interface/src/CrashReporter.cpp index 1fd534ffac..596c34ca92 100644 --- a/interface/src/CrashReporter.cpp +++ b/interface/src/CrashReporter.cpp @@ -10,16 +10,20 @@ // -#ifdef HAS_BUGSPLAT +#include "Application.h" #include "CrashReporter.h" +#ifdef _WIN32 #include #include +#include #include - #include + +#pragma comment(lib, "Dbghelp.lib") + // SetUnhandledExceptionFilter can be overridden by the CRT at the point that an error occurs. More information // can be found here: http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li // A fairly common approach is to patch the SetUnhandledExceptionFilter so that it cannot be overridden and so @@ -77,13 +81,37 @@ BOOL redirectLibraryFunctionToFunction(char* library, char* function, void* fn) return bRet; } +void printStackTrace(ULONG framesToSkip = 1) { + HANDLE process = GetCurrentProcess(); + SymInitialize(process, NULL, TRUE); + void* stack[100]; + uint16_t frames = CaptureStackBackTrace(framesToSkip, 100, stack, NULL); + SYMBOL_INFO* symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1); + symbol->MaxNameLen = 255; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + + for (uint16_t i = 0; i < frames; ++i) { + SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol); + qWarning() << QString("%1: %2 - 0x%0X").arg(QString::number(frames - i - 1), QString(symbol->Name), QString::number(symbol->Address, 16)); + } + + free(symbol); + + // Try to force the log to sync to the filesystem + auto app = qApp; + if (app && app->getLogger()) { + app->getLogger()->sync(); + } +} void handleSignal(int signal) { // Throw so BugSplat can handle throw(signal); } -void handlePureVirtualCall() { +void __cdecl handlePureVirtualCall() { + qWarning() << "Pure virtual function call detected"; + printStackTrace(2); // Throw so BugSplat can handle throw("ERROR: Pure virtual call"); } @@ -107,6 +135,8 @@ _purecall_handler __cdecl noop_set_purecall_handler(_purecall_handler pNew) { return nullptr; } +#ifdef HAS_BUGSPLAT + static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY; CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version) @@ -133,3 +163,4 @@ CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicati } } #endif +#endif diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index a8b0a265c9..4051bd8a1e 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -35,9 +35,9 @@ const QString API_USER_HEARTBEAT_PATH = "/api/v1/user/heartbeat"; const QString SESSION_ID_KEY = "session_id"; void DiscoverabilityManager::updateLocation() { - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); - if (_mode.get() != Discoverability::None && accountManager.isLoggedIn()) { + if (_mode.get() != Discoverability::None && accountManager->isLoggedIn()) { auto addressManager = DependencyManager::get(); DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); @@ -79,9 +79,6 @@ void DiscoverabilityManager::updateLocation() { const QString FRIENDS_ONLY_KEY_IN_LOCATION = "friends_only"; locationObject.insert(FRIENDS_ONLY_KEY_IN_LOCATION, (_mode.get() == Discoverability::Friends)); - // if we have a session ID add it now, otherwise add a null value - rootObject[SESSION_ID_KEY] = _sessionID.isEmpty() ? QJsonValue() : _sessionID; - JSONCallbackParameters callbackParameters; callbackParameters.jsonCallbackReceiver = this; callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse"; @@ -98,7 +95,7 @@ void DiscoverabilityManager::updateLocation() { apiPath = API_USER_LOCATION_PATH; } - accountManager.sendRequest(apiPath, AccountManagerAuth::Required, + accountManager->sendRequest(apiPath, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, callbackParameters, QJsonDocument(rootObject).toJson()); @@ -109,16 +106,8 @@ void DiscoverabilityManager::updateLocation() { callbackParameters.jsonCallbackReceiver = this; callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse"; - QJsonObject heartbeatObject; - if (!_sessionID.isEmpty()) { - heartbeatObject[SESSION_ID_KEY] = _sessionID; - } else { - heartbeatObject[SESSION_ID_KEY] = QJsonValue(); - } - - accountManager.sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional, - QNetworkAccessManager::PutOperation, callbackParameters, - QJsonDocument(heartbeatObject).toJson()); + accountManager->sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional, + QNetworkAccessManager::PutOperation, callbackParameters); } } @@ -126,13 +115,17 @@ void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply auto dataObject = AccountManager::dataObjectFromResponse(requestReply); if (!dataObject.isEmpty()) { - _sessionID = dataObject[SESSION_ID_KEY].toString(); + auto sessionID = dataObject[SESSION_ID_KEY].toString(); + + // give that session ID to the account manager + auto accountManager = DependencyManager::get(); + accountManager->setSessionID(sessionID); } } void DiscoverabilityManager::removeLocation() { - AccountManager& accountManager = AccountManager::getInstance(); - accountManager.sendRequest(API_USER_LOCATION_PATH, AccountManagerAuth::Required, QNetworkAccessManager::DeleteOperation); + auto accountManager = DependencyManager::get(); + accountManager->sendRequest(API_USER_LOCATION_PATH, AccountManagerAuth::Required, QNetworkAccessManager::DeleteOperation); } void DiscoverabilityManager::setDiscoverabilityMode(Discoverability::Mode discoverabilityMode) { diff --git a/interface/src/DiscoverabilityManager.h b/interface/src/DiscoverabilityManager.h index 9a1fa7b39c..196b0cdf81 100644 --- a/interface/src/DiscoverabilityManager.h +++ b/interface/src/DiscoverabilityManager.h @@ -49,7 +49,6 @@ private: DiscoverabilityManager(); Setting::Handle _mode; - QString _sessionID; QJsonObject _lastLocationObject; }; diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp index f5f7ce6ebe..754fa7f474 100644 --- a/interface/src/FileLogger.cpp +++ b/interface/src/FileLogger.cpp @@ -24,9 +24,14 @@ static const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; static const QString LOGS_DIRECTORY = "Logs"; +static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; +static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]"; +static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt"; // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively // small so it doesn't go over the 2MB zipped limit for all of the files we send. static const qint64 MAX_LOG_SIZE = 512 * 1024; +// Max log files found in the log directory is 100. +static const qint64 MAX_LOG_DIR_SIZE = 512 * 1024 * 100; // Max log age is 1 hour static const uint64_t MAX_LOG_AGE_USECS = USECS_PER_SECOND * 3600; @@ -71,6 +76,22 @@ void FilePersistThread::rollFileIfNecessary(QFile& file, bool notifyListenersIfR _lastRollTime = now; } + QStringList nameFilters; + nameFilters << FILENAME_WILDCARD; + + QDir logQDir(FileUtils::standardPath(LOGS_DIRECTORY)); + logQDir.setNameFilters(nameFilters); + logQDir.setSorting(QDir::Time); + QFileInfoList filesInDir = logQDir.entryInfoList(); + qint64 totalSizeOfDir = 0; + foreach(QFileInfo dirItm, filesInDir){ + if (totalSizeOfDir < MAX_LOG_DIR_SIZE){ + totalSizeOfDir += dirItm.size(); + } else { + QFile file(dirItm.filePath()); + file.remove(); + } + } } } @@ -115,3 +136,7 @@ QString FileLogger::getLogData() { } return result; } + +void FileLogger::sync() { + _persistThreadInstance->waitIdle(); +} diff --git a/interface/src/FileLogger.h b/interface/src/FileLogger.h index e9bae63a73..28ac6fba40 100644 --- a/interface/src/FileLogger.h +++ b/interface/src/FileLogger.h @@ -28,6 +28,7 @@ public: virtual void addMessage(const QString&) override; virtual QString getLogData() override; virtual void locateLog() override; + void sync(); signals: void rollingLogFile(QString newFilename); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 05bb03bf86..6308ac6c73 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -34,7 +34,6 @@ #include "avatar/AvatarManager.h" #include "devices/DdeFaceTracker.h" #include "devices/Faceshift.h" -#include "input-plugins/SpacemouseManager.h" #include "MainWindow.h" #include "render/DrawStatus.h" #include "scripting/MenuScriptingInterface.h" @@ -54,7 +53,7 @@ Menu* Menu::getInstance() { Menu::Menu() { auto dialogsManager = DependencyManager::get(); - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); // File/Application menu ---------------------------------- MenuWrapper* fileMenu = addMenu("File"); @@ -64,9 +63,9 @@ Menu::Menu() { addActionToQMenuAndActionHash(fileMenu, MenuOption::Login); // connect to the appropriate signal of the AccountManager so that we can change the Login/Logout menu item - connect(&accountManager, &AccountManager::profileChanged, + connect(accountManager.data(), &AccountManager::profileChanged, dialogsManager.data(), &DialogsManager::toggleLoginDialog); - connect(&accountManager, &AccountManager::logoutComplete, + connect(accountManager.data(), &AccountManager::logoutComplete, dialogsManager.data(), &DialogsManager::toggleLoginDialog); } @@ -137,8 +136,8 @@ Menu::Menu() { Qt::CTRL | Qt::SHIFT | Qt::Key_A, qApp, SLOT(toggleAssetServerWidget())); auto nodeList = DependencyManager::get(); - QObject::connect(nodeList.data(), &NodeList::canRezChanged, assetServerAction, &QAction::setEnabled); - assetServerAction->setEnabled(nodeList->getThisNodeCanRez()); + QObject::connect(nodeList.data(), &NodeList::canWriteAssetsChanged, assetServerAction, &QAction::setEnabled); + assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets()); // Edit > Package Model... [advanced] addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0, @@ -257,8 +256,7 @@ Menu::Menu() { UNSPECIFIED_POSITION, "Advanced"); // View > Overlays - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true, - qApp, SLOT(setOverlaysVisible(bool))); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true); // Navigate menu ---------------------------------- MenuWrapper* navigateMenu = addMenu("Navigate"); @@ -327,12 +325,6 @@ Menu::Menu() { connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); #endif - // Settings > Input Devices - MenuWrapper* inputModeMenu = addMenu(MenuOption::InputMenu, "Advanced"); - QActionGroup* inputModeGroup = new QActionGroup(inputModeMenu); - inputModeGroup->setExclusive(false); - - // Developer menu ---------------------------------- MenuWrapper* developerMenu = addMenu("Developer", "Developer"); @@ -531,9 +523,6 @@ Menu::Menu() { // Developer > Network >>> MenuWrapper* networkMenu = developerMenu->addMenu("Network"); addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); - addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false, - qApp->getEntityEditPacketSender(), - SLOT(toggleNackPackets())); addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableActivityLogger, 0, @@ -549,6 +538,16 @@ Menu::Menu() { addActionToQMenuAndActionHash(networkMenu, MenuOption::BandwidthDetails, 0, dialogsManager.data(), SLOT(bandwidthDetails())); + #if (PR_BUILD || DEV_BUILD) + addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongProtocolVersion, 0, false, + qApp, SLOT(sendWrongProtocolVersionsSignature(bool))); + + addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongDSConnectVersion, 0, false, + nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool))); + #endif + + + // Developer > Timing >>> MenuWrapper* timingMenu = developerMenu->addMenu("Timing"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 484be9f346..503cbf51fa 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -84,7 +84,6 @@ namespace MenuOption { const QString DisableActivityLogger = "Disable Activity Logger"; const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment"; const QString DisableLightEntities = "Disable Light Entities"; - const QString DisableNackPackets = "Disable Entity NACK Packets"; const QString DiskCacheEditor = "Disk Cache Editor"; const QString DisplayCrashOptions = "Display Crash Options"; const QString DisplayHandTargets = "Show Hand Targets"; @@ -114,7 +113,6 @@ namespace MenuOption { const QString Help = "Help..."; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IndependentMode = "Independent Mode"; - const QString InputMenu = "Avatar>Input Devices"; const QString ActionMotorControl = "Enable Default Motor Control"; const QString LeapMotionOnHMD = "Leap Motion on HMD"; const QString LoadScript = "Open and Run Script File..."; @@ -168,6 +166,8 @@ namespace MenuOption { const QString RunTimingTests = "Run Timing Tests"; const QString ScriptEditor = "Script Editor..."; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; + const QString SendWrongDSConnectVersion = "Send wrong DS connect version"; + const QString SendWrongProtocolVersion = "Send wrong protocol version"; const QString SetHomeLocation = "Set Home Location"; const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowBordersEntityNodes = "Show Entity Nodes"; diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp deleted file mode 100644 index b651a1520d..0000000000 --- a/interface/src/PluginContainerProxy.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "PluginContainerProxy.h" - -#include -#include - -#include -#include -#include -#include -#include - -#include "Application.h" -#include "MainWindow.h" -#include "GLCanvas.h" -#include "ui/DialogsManager.h" - -#include -#include - -PluginContainerProxy::PluginContainerProxy() { -} - -PluginContainerProxy::~PluginContainerProxy() { -} - -ui::Menu* PluginContainerProxy::getPrimaryMenu() { - auto appMenu = qApp->_window->menuBar(); - auto uiMenu = dynamic_cast(appMenu); - return uiMenu; -} - -bool PluginContainerProxy::isForeground() { - return qApp->isForeground() && !qApp->getWindow()->isMinimized(); -} - -void PluginContainerProxy::requestReset() { - // We could signal qApp to sequence this, but it turns out that requestReset is only used from within the main thread anyway. - qApp->resetSensors(true); -} - -void PluginContainerProxy::showDisplayPluginsTools(bool show) { - DependencyManager::get()->hmdTools(show); -} - -GLWidget* PluginContainerProxy::getPrimaryWidget() { - return qApp->_glWidget; -} - -MainWindow* PluginContainerProxy::getPrimaryWindow() { - return qApp->getWindow(); -} - -QOpenGLContext* PluginContainerProxy::getPrimaryContext() { - return qApp->_glWidget->context()->contextHandle(); -} - -const DisplayPluginPointer PluginContainerProxy::getActiveDisplayPlugin() const { - return qApp->getActiveDisplayPlugin(); -} - -bool PluginContainerProxy::makeRenderingContextCurrent() { - return qApp->_offscreenContext->makeCurrent(); -} - -void PluginContainerProxy::releaseSceneTexture(const gpu::TexturePointer& 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(const gpu::TexturePointer& texture) { - qApp->_applicationOverlay.releaseOverlay(texture); -} - diff --git a/interface/src/PluginContainerProxy.h b/interface/src/PluginContainerProxy.h deleted file mode 100644 index a04a1b2977..0000000000 --- a/interface/src/PluginContainerProxy.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#ifndef hifi_PluginContainerProxy_h -#define hifi_PluginContainerProxy_h - -#include -#include - -#include -#include - -class QActionGroup; - -class PluginContainerProxy : public QObject, PluginContainer { - Q_OBJECT - PluginContainerProxy(); - virtual ~PluginContainerProxy(); - virtual void showDisplayPluginsTools(bool show = true) override; - virtual void requestReset() override; - virtual bool makeRenderingContextCurrent() override; - virtual void releaseSceneTexture(const gpu::TexturePointer& texture) override; - virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) override; - virtual GLWidget* getPrimaryWidget() override; - virtual MainWindow* getPrimaryWindow() override; - virtual ui::Menu* getPrimaryMenu() override; - virtual QOpenGLContext* getPrimaryContext() override; - virtual bool isForeground() override; - virtual const DisplayPluginPointer getActiveDisplayPlugin() const override; - - friend class Application; - -}; - -#endif diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 9ae636af36..4d9481f002 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "Application.h" @@ -83,7 +84,6 @@ Avatar::Avatar(RigPointer rig) : _acceleration(0.0f), _lastAngularVelocity(0.0f), _lastOrientation(), - _leanScale(0.5f), _worldUpDirection(DEFAULT_UP_DIRECTION), _moving(false), _initialized(false), @@ -102,6 +102,18 @@ Avatar::Avatar(RigPointer rig) : Avatar::~Avatar() { assert(isDead()); // mark dead before calling the dtor + + EntityTreeRenderer* treeRenderer = qApp->getEntities(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (entityTree) { + entityTree->withWriteLock([&] { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + entityTree->deleteEntity(entityID, true, true); + } + }); + } + if (_motionState) { delete _motionState; _motionState = nullptr; @@ -157,6 +169,92 @@ void Avatar::animateScaleChanges(float deltaTime) { } } +void Avatar::updateAvatarEntities() { + // - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity() + // - updateAvatarEntity saves the bytes and sets _avatarEntityDataLocallyEdited + // - MyAvatar::update notices _avatarEntityDataLocallyEdited and calls sendIdentityPacket + // - sendIdentityPacket sends the entity bytes to the server which relays them to other interfaces + // - AvatarHashMap::processAvatarIdentityPacket on other interfaces call avatar->setAvatarEntityData() + // - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true + // - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are... + + if (!_avatarEntityDataChanged) { + return; + } + + if (getID() == QUuid()) { + return; // wait until MyAvatar gets an ID before doing this. + } + + EntityTreeRenderer* treeRenderer = qApp->getEntities(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (!entityTree) { + return; + } + + bool success = true; + QScriptEngine scriptEngine; + entityTree->withWriteLock([&] { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + // see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties + // and either add or update the entity. + QByteArray jsonByteArray = avatarEntities.value(entityID); + QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(jsonByteArray); + if (!jsonProperties.isObject()) { + qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(jsonByteArray.toHex()); + continue; + } + + QVariant variantProperties = jsonProperties.toVariant(); + QVariantMap asMap = variantProperties.toMap(); + QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); + EntityItemProperties properties; + EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, properties); + properties.setClientOnly(true); + properties.setOwningAvatarID(getID()); + + // there's no entity-server to tell us we're the simulation owner, so always set the + // simulationOwner to the owningAvatarID and a high priority. + properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY); + + if (properties.getParentID() == AVATAR_SELF_ID) { + properties.setParentID(getID()); + } + + EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); + + if (entity) { + if (entityTree->updateEntity(entityID, properties)) { + entity->updateLastEditedFromRemote(); + } else { + success = false; + } + } else { + entity = entityTree->addEntity(entityID, properties); + if (!entity) { + success = false; + } + } + } + + AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs(); + _avatarEntitiesLock.withReadLock([&] { + foreach (auto entityID, recentlyDettachedAvatarEntities) { + if (!_avatarEntityData.contains(entityID)) { + entityTree->deleteEntity(entityID, true, true); + } + } + }); + }); + + if (success) { + setAvatarEntityDataChanged(false); + } +} + + + void Avatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); @@ -206,6 +304,9 @@ void Avatar::simulate(float deltaTime) { head->setScale(getUniformScale()); head->simulate(deltaTime, false, !_shouldAnimate); } + } else { + // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. + _skeletonModel->simulate(deltaTime, false); } // update animation for display name fade in/out @@ -229,6 +330,7 @@ void Avatar::simulate(float deltaTime) { simulateAttachments(deltaTime); updatePalms(); + updateAvatarEntities(); } bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const { @@ -543,10 +645,6 @@ void Avatar::simulateAttachments(float deltaTime) { } } -void Avatar::updateJointMappings() { - // no-op; joint mappings come from skeleton model -} - float Avatar::getBoundingRadius() const { return getBounds().getLargestDimension() / 2.0f; } @@ -987,6 +1085,15 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { shapeInfo.setOffset(uniformScale * _skeletonModel->getBoundingCapsuleOffset()); } +void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { + ShapeInfo shapeInfo; + computeShapeInfo(shapeInfo); + glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight + start = getPosition() - glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset(); + end = getPosition() + glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset(); + radius = halfExtents.x; +} + void Avatar::setMotionState(AvatarMotionState* motionState) { _motionState = motionState; } @@ -1088,7 +1195,7 @@ void Avatar::setParentID(const QUuid& parentID) { if (success) { setTransform(beforeChangeTransform, success); if (!success) { - qDebug() << "Avatar::setParentID failed to reset avatar's location."; + qCDebug(interfaceapp) << "Avatar::setParentID failed to reset avatar's location."; } } } @@ -1103,7 +1210,7 @@ void Avatar::setParentJointIndex(quint16 parentJointIndex) { if (success) { setTransform(beforeChangeTransform, success); if (!success) { - qDebug() << "Avatar::setParentJointIndex failed to reset avatar's location."; + qCDebug(interfaceapp) << "Avatar::setParentJointIndex failed to reset avatar's location."; } } } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 2580ac1d37..b9f44613c7 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -64,6 +64,7 @@ public: typedef std::shared_ptr PayloadPointer; void init(); + void updateAvatarEntities(); void simulate(float deltaTime); virtual void simulateAttachments(float deltaTime); @@ -153,6 +154,7 @@ public: virtual void rebuildCollisionShape(); virtual void computeShapeInfo(ShapeInfo& shapeInfo); + void getCapsule(glm::vec3& start, glm::vec3& end, float& radius); AvatarMotionState* getMotionState() { return _motionState; } @@ -209,7 +211,6 @@ protected: glm::vec3 _angularAcceleration; glm::quat _lastOrientation; - float _leanScale; glm::vec3 _worldUpDirection; float _stringLength; bool _moving; ///< set when position is changing @@ -235,8 +236,6 @@ protected: virtual bool shouldRenderHead(const RenderArgs* renderArgs) const; virtual void fixupModelsInScene(); - virtual void updateJointMappings() override; - virtual void updatePalms(); render::ItemID _renderItemID{ render::Item::INVALID_ITEM_ID }; diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index c84cfecb40..5acee052f2 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -17,11 +17,14 @@ #include "CharacterController.h" const uint16_t AvatarActionHold::holdVersion = 1; +const int AvatarActionHold::velocitySmoothFrames = 6; + AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity) : ObjectActionSpring(id, ownerEntity) { _type = ACTION_TYPE_HOLD; + _measuredLinearVelocities.resize(AvatarActionHold::velocitySmoothFrames); #if WANT_DEBUG qDebug() << "AvatarActionHold::AvatarActionHold"; #endif @@ -204,8 +207,40 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { } withWriteLock([&]{ + if (_previousSet && + _positionalTarget != _previousPositionalTarget) { // don't average in a zero velocity if we get the same data + glm::vec3 oneFrameVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep; + + _measuredLinearVelocities[_measuredLinearVelocitiesIndex++] = oneFrameVelocity; + if (_measuredLinearVelocitiesIndex >= AvatarActionHold::velocitySmoothFrames) { + _measuredLinearVelocitiesIndex = 0; + } + } + + glm::vec3 measuredLinearVelocity; + for (int i = 0; i < AvatarActionHold::velocitySmoothFrames; i++) { + // there is a bit of lag between when someone releases the trigger and when the software reacts to + // the release. we calculate the velocity from previous frames but we don't include several + // of the most recent. + // + // if _measuredLinearVelocitiesIndex is + // 0 -- ignore i of 3 4 5 + // 1 -- ignore i of 4 5 0 + // 2 -- ignore i of 5 0 1 + // 3 -- ignore i of 0 1 2 + // 4 -- ignore i of 1 2 3 + // 5 -- ignore i of 2 3 4 + if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || + (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || + (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) { + continue; + } + measuredLinearVelocity += _measuredLinearVelocities[i]; + } + measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames - 3); // 3 because of the 3 we skipped, above + if (_kinematicSetVelocity) { - rigidBody->setLinearVelocity(glmToBullet(_linearVelocityTarget)); + rigidBody->setLinearVelocity(glmToBullet(measuredLinearVelocity)); rigidBody->setAngularVelocity(glmToBullet(_angularVelocityTarget)); } diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index 609fd57ff3..bfa392172d 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -64,6 +64,10 @@ private: glm::vec3 _palmOffsetFromRigidBody; // leaving this here for future refernece. // glm::quat _palmRotationFromRigidBody; + + static const int velocitySmoothFrames; + QVector _measuredLinearVelocities; + int _measuredLinearVelocitiesIndex { 0 }; }; #endif // hifi_AvatarActionHold_h diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index ddadcb3909..bd76d2bd81 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include "Application.h" @@ -69,10 +70,15 @@ AvatarManager::AvatarManager(QObject* parent) : // register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar qRegisterMetaType >("NodeWeakPointer"); - auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + auto nodeList = DependencyManager::get(); + auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar"); packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket"); + + // when we hear that the user has ignored an avatar by session UUID + // immediately remove that avatar instead of waiting for the absence of packets from avatar mixer + connect(nodeList.data(), &NodeList::ignoredNode, this, &AvatarManager::removeAvatar); } AvatarManager::~AvatarManager() { @@ -85,7 +91,8 @@ void AvatarManager::init() { _avatarHash.insert(MY_AVATAR_KEY, _myAvatar); } - connect(DependencyManager::get().data(), &SceneScriptingInterface::shouldRenderAvatarsChanged, this, &AvatarManager::updateAvatarRenderStatus, Qt::QueuedConnection); + connect(DependencyManager::get().data(), &SceneScriptingInterface::shouldRenderAvatarsChanged, + this, &AvatarManager::updateAvatarRenderStatus, Qt::QueuedConnection); render::ScenePointer scene = qApp->getMain3DScene(); render::PendingChanges pendingChanges; @@ -322,7 +329,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents const auto characterController = myAvatar->getCharacterController(); const float avatarVelocityChange = (characterController ? glm::length(characterController->getVelocityChange()) : 0.0f); const float velocityChange = glm::length(collision.velocityChange) + avatarVelocityChange; - const float MIN_AVATAR_COLLISION_ACCELERATION = 0.01f; + const float MIN_AVATAR_COLLISION_ACCELERATION = 2.4f; // walking speed const bool isSound = (collision.type == CONTACT_EVENT_TYPE_START) && (velocityChange > MIN_AVATAR_COLLISION_ACCELERATION); if (!isSound) { @@ -330,14 +337,24 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents } // Your avatar sound is personal to you, so let's say the "mass" part of the kinetic energy is already accounted for. const float energy = velocityChange * velocityChange; - const float COLLISION_ENERGY_AT_FULL_VOLUME = 0.5f; + const float COLLISION_ENERGY_AT_FULL_VOLUME = 10.0f; const float energyFactorOfFull = fmin(1.0f, energy / COLLISION_ENERGY_AT_FULL_VOLUME); // For general entity collisionSoundURL, playSound supports changing the pitch for the sound based on the size of the object, // but most avatars are roughly the same size, so let's not be so fancy yet. const float AVATAR_STRETCH_FACTOR = 1.0f; - AudioInjector::playSound(collisionSound, energyFactorOfFull, AVATAR_STRETCH_FACTOR, myAvatar->getPosition()); + + _collisionInjectors.remove_if([](QPointer& injector) { + return !injector || injector->isFinished(); + }); + + static const int MAX_INJECTOR_COUNT = 3; + if (_collisionInjectors.size() < MAX_INJECTOR_COUNT) { + auto injector = AudioInjector::playSound(collisionSound, energyFactorOfFull, AVATAR_STRETCH_FACTOR, + myAvatar->getPosition()); + _collisionInjectors.emplace_back(injector); + } myAvatar->collisionWithEntity(collision); return; } @@ -388,3 +405,76 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) return findAvatar(sessionID); } + +RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray, + const QScriptValue& avatarIdsToInclude, + const QScriptValue& avatarIdsToDiscard) { + RayToAvatarIntersectionResult result; + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(const_cast(this), "findRayIntersection", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(RayToAvatarIntersectionResult, result), + Q_ARG(const PickRay&, ray), + Q_ARG(const QScriptValue&, avatarIdsToInclude), + Q_ARG(const QScriptValue&, avatarIdsToDiscard)); + return result; + } + + QVector avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude); + QVector avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard); + + glm::vec3 normDirection = glm::normalize(ray.direction); + + for (auto avatarData : _avatarHash) { + auto avatar = std::static_pointer_cast(avatarData); + if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) || + (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) { + continue; + } + + float distance; + BoxFace face; + glm::vec3 surfaceNormal; + + SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); + + // It's better to intersect the ray against the avatar's actual mesh, but this is currently difficult to + // do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code + // intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking + // against the avatar is sort-of right, but you likely wont be able to pick against the arms. + + // TODO -- find a way to extract transformed avatar mesh data from the rendering engine. + + // if we weren't picking against the capsule, we would want to pick against the avatarBounds... + // AABox avatarBounds = avatarModel->getRenderableMeshBound(); + // if (!avatarBounds.findRayIntersection(ray.origin, normDirection, distance, face, surfaceNormal)) { + // // ray doesn't intersect avatar's bounding-box + // continue; + // } + + glm::vec3 start; + glm::vec3 end; + float radius; + avatar->getCapsule(start, end, radius); + bool intersects = findRayCapsuleIntersection(ray.origin, normDirection, start, end, radius, distance); + if (!intersects) { + // ray doesn't intersect avatar's capsule + continue; + } + + QString extraInfo; + intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection, + distance, face, surfaceNormal, extraInfo, true); + + if (intersects && (!result.intersects || distance < result.distance)) { + result.intersects = true; + result.avatarID = avatar->getID(); + result.distance = distance; + } + } + + if (result.intersects) { + result.intersection = ray.origin + normDirection * result.distance; + } + + return result; +} diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 1cd295d69f..f09aa9791c 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -25,6 +25,7 @@ #include "AvatarMotionState.h" class MyAvatar; +class AudioInjector; class AvatarManager : public AvatarHashMap { Q_OBJECT @@ -69,10 +70,17 @@ public: void addAvatarToSimulation(Avatar* avatar); + Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, + const QScriptValue& avatarIdsToInclude = QScriptValue(), + const QScriptValue& avatarIdsToDiscard = QScriptValue()); + public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } void updateAvatarRenderStatus(bool shouldRenderAvatars); +private slots: + virtual void removeAvatar(const QUuid& sessionUUID) override; + private: explicit AvatarManager(QObject* parent = 0); explicit AvatarManager(const AvatarManager& other); @@ -83,7 +91,6 @@ private: virtual AvatarSharedPointer newSharedAvatar() override; virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) override; - virtual void removeAvatar(const QUuid& sessionUUID) override; virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) override; QVector _avatarFades; @@ -94,6 +101,8 @@ private: bool _shouldShowReceiveStats = false; + std::list> _collisionInjectors; + SetOfAvatarMotionStates _motionStatesThatMightUpdate; SetOfMotionStates _motionStatesToAddToPhysics; VectorOfMotionStates _motionStatesToRemoveFromPhysics; diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 3af8b8a423..928f46facb 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -54,8 +54,6 @@ Head::Head(Avatar* owningAvatar) : _deltaPitch(0.0f), _deltaYaw(0.0f), _deltaRoll(0.0f), - _deltaLeanSideways(0.0f), - _deltaLeanForward(0.0f), _isCameraMoving(false), _isLookingAtMe(false), _lookingAtMeStarted(0), @@ -70,7 +68,6 @@ void Head::init() { void Head::reset() { _baseYaw = _basePitch = _baseRoll = 0.0f; - _leanForward = _leanSideways = 0.0f; } void Head::simulate(float deltaTime, bool isMine, bool billboard) { @@ -118,13 +115,6 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { auto eyeTracker = DependencyManager::get(); _isEyeTrackerConnected = eyeTracker->isTracking(); } - - // Twist the upper body to follow the rotation of the head, but only do this with my avatar, - // since everyone else will see the full joint rotations for other people. - const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f; - const float BODY_FOLLOW_HEAD_FACTOR = 0.66f; - float currentTwist = getTorsoTwist(); - setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE); } if (!(_isFaceTrackerConnected || billboard)) { @@ -301,17 +291,13 @@ void Head::applyEyelidOffset(glm::quat headOrientation) { } } -void Head::relaxLean(float deltaTime) { +void Head::relax(float deltaTime) { // restore rotation, lean to neutral positions const float LEAN_RELAXATION_PERIOD = 0.25f; // seconds float relaxationFactor = 1.0f - glm::min(deltaTime / LEAN_RELAXATION_PERIOD, 1.0f); _deltaYaw *= relaxationFactor; _deltaPitch *= relaxationFactor; _deltaRoll *= relaxationFactor; - _leanSideways *= relaxationFactor; - _leanForward *= relaxationFactor; - _deltaLeanSideways *= relaxationFactor; - _deltaLeanForward *= relaxationFactor; } void Head::setScale (float scale) { @@ -419,8 +405,3 @@ float Head::getFinalPitch() const { float Head::getFinalRoll() const { return glm::clamp(_baseRoll + _deltaRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); } - -void Head::addLeanDeltas(float sideways, float forward) { - _deltaLeanSideways += sideways; - _deltaLeanForward += forward; -} diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index e4b8fefea5..33ea180d33 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -59,8 +59,6 @@ public: glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } glm::vec3 getUpDirection() const { return getOrientation() * IDENTITY_UP; } glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; } - float getFinalLeanSideways() const { return _leanSideways + _deltaLeanSideways; } - float getFinalLeanForward() const { return _leanForward + _deltaLeanForward; } glm::quat getEyeRotation(const glm::vec3& eyePosition) const; @@ -91,8 +89,7 @@ public: virtual float getFinalYaw() const; virtual float getFinalRoll() const; - void relaxLean(float deltaTime); - void addLeanDeltas(float sideways, float forward); + void relax(float deltaTime); float getTimeWithoutTalking() const { return _timeWithoutTalking; } @@ -132,10 +129,6 @@ private: float _deltaYaw; float _deltaRoll; - // delta lean angles for lean perturbations (driven by collisions) - float _deltaLeanSideways; - float _deltaLeanForward; - bool _isCameraMoving; bool _isLookingAtMe; quint64 _lookingAtMeStarted; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7d08367c8d..6a69ee9a9a 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -190,9 +190,6 @@ MyAvatar::MyAvatar(RigPointer rig) : if (!headData->getBlendshapeCoefficients().isEmpty()) { _headData->setBlendshapeCoefficients(headData->getBlendshapeCoefficients()); } - // head lean - _headData->setLeanForward(headData->getLeanForward()); - _headData->setLeanSideways(headData->getLeanSideways()); // head orientation _headData->setLookAtPosition(headData->getLookAtPosition()); } @@ -234,18 +231,22 @@ QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { return AvatarData::toByteArray(cullSmallChanges, sendAll); } -void MyAvatar::reset(bool andRecenter) { +void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, andRecenter)); + QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, andRecenter), Q_ARG(bool, andReload), Q_ARG(bool, andHead)); return; } // Reset dynamic state. _wasPushing = _isPushing = _isBraking = false; _follow.deactivate(); - _skeletonModel->reset(); - getHead()->reset(); + if (andReload) { + _skeletonModel->reset(); + } + if (andHead) { // which drives camera in desktop + getHead()->reset(); + } setThrust(glm::vec3(0.0f)); if (andRecenter) { @@ -261,8 +262,9 @@ void MyAvatar::reset(bool andRecenter) { setPosition(worldBodyPos); setOrientation(worldBodyRot); - // now sample the new hmd orientation AFTER sensor reset. - updateFromHMDSensorMatrix(qApp->getHMDSensorPose()); + // now sample the new hmd orientation AFTER sensor reset, which should be identity. + glm::mat4 identity; + updateFromHMDSensorMatrix(identity); // update the body in sensor space using the new hmd sensor sample _bodySensorMatrix = deriveBodyFromHMDSensor(); @@ -301,7 +303,7 @@ void MyAvatar::update(float deltaTime) { } Head* head = getHead(); - head->relaxLean(deltaTime); + head->relax(deltaTime); updateFromTrackers(deltaTime); // Get audio loudness data from audio input device @@ -309,6 +311,10 @@ void MyAvatar::update(float deltaTime) { head->setAudioLoudness(audio->getLastInputLoudness()); head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); + if (_avatarEntityDataLocallyEdited) { + sendIdentityPacket(); + } + simulate(deltaTime); currentEnergy += energyChargeRate; @@ -424,7 +430,14 @@ void MyAvatar::simulate(float deltaTime) { EntityTreeRenderer* entityTreeRenderer = qApp->getEntities(); EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; if (entityTree) { + bool flyingAllowed = true; + bool ghostingAllowed = true; entityTree->withWriteLock([&] { + std::shared_ptr zone = entityTreeRenderer->myAvatarZone(); + if (zone) { + flyingAllowed = zone->getFlyingAllowed(); + ghostingAllowed = zone->getGhostingAllowed(); + } auto now = usecTimestampNow(); EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); MovingEntitiesOperator moveOperator(entityTree); @@ -441,7 +454,8 @@ void MyAvatar::simulate(float deltaTime) { EntityItemProperties properties = entity->getProperties(); properties.setQueryAACubeDirty(); properties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); + + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties); entity->setLastBroadcast(usecTimestampNow()); } } @@ -452,7 +466,13 @@ void MyAvatar::simulate(float deltaTime) { entityTree->recurseTreeWithOperator(&moveOperator); } }); + _characterController.setFlyingAllowed(flyingAllowed); + if (!_characterController.isEnabled() && !ghostingAllowed) { + _characterController.setEnabled(true); + } } + + updateAvatarEntities(); } // thread-safe @@ -551,16 +571,6 @@ void MyAvatar::updateFromTrackers(float deltaTime) { head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView); head->setDeltaRoll(estimatedRotation.z); } - - // Update torso lean distance based on accelerometer data - const float TORSO_LENGTH = 0.5f; - glm::vec3 relativePosition = estimatedPosition - glm::vec3(0.0f, -TORSO_LENGTH, 0.0f); - - const float MAX_LEAN = 45.0f; - head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)), - -MAX_LEAN, MAX_LEAN)); - head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)), - -MAX_LEAN, MAX_LEAN)); } glm::vec3 MyAvatar::getLeftHandPosition() const { @@ -669,9 +679,6 @@ void MyAvatar::saveData() { settings.setValue("headPitch", getHead()->getBasePitch()); - settings.setValue("pupilDilation", getHead()->getPupilDilation()); - - settings.setValue("leanScale", _leanScale); settings.setValue("scale", _targetScale); settings.setValue("fullAvatarURL", @@ -700,14 +707,27 @@ void MyAvatar::saveData() { } settings.endArray(); + settings.beginWriteArray("avatarEntityData"); + int avatarEntityIndex = 0; + _avatarEntitiesLock.withReadLock([&] { + for (auto entityID : _avatarEntityData.keys()) { + settings.setArrayIndex(avatarEntityIndex); + settings.setValue("id", entityID); + settings.setValue("properties", _avatarEntityData.value(entityID)); + avatarEntityIndex++; + } + }); + settings.endArray(); + settings.setValue("displayName", _displayName); settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); + settings.setValue("clearOverlayWhenMoving", _clearOverlayWhenMoving); settings.endGroup(); } -float loadSetting(QSettings& settings, const char* name, float defaultValue) { +float loadSetting(Settings& settings, const QString& name, float defaultValue) { float value = settings.value(name, defaultValue).toFloat(); if (glm::isnan(value)) { value = defaultValue; @@ -777,9 +797,6 @@ void MyAvatar::loadData() { getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f)); - getHead()->setPupilDilation(loadSetting(settings, "pupilDilation", 0.0f)); - - _leanScale = loadSetting(settings, "leanScale", 0.05f); _targetScale = loadSetting(settings, "scale", 1.0f); setScale(glm::vec3(_targetScale)); @@ -811,9 +828,21 @@ void MyAvatar::loadData() { settings.endArray(); setAttachmentData(attachmentData); + int avatarEntityCount = settings.beginReadArray("avatarEntityData"); + for (int i = 0; i < avatarEntityCount; i++) { + settings.setArrayIndex(i); + QUuid entityID = settings.value("id").toUuid(); + // QUuid entityID = QUuid::createUuid(); // generate a new ID + QByteArray properties = settings.value("properties").toByteArray(); + updateAvatarEntity(entityID, properties); + } + settings.endArray(); + setAvatarEntityDataChanged(true); + setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); + setClearOverlayWhenMoving(settings.value("clearOverlayWhenMoving", _clearOverlayWhenMoving).toBool()); settings.endGroup(); @@ -1172,7 +1201,10 @@ void MyAvatar::updateMotors() { if (_characterController.getState() == CharacterController::State::Hover) { motorRotation = getHead()->getCameraOrientation(); } else { - motorRotation = getOrientation(); + // non-hovering = walking: follow camera twist about vertical but not lift + // so we decompose camera's rotation and store the twist part in motorRotation + glm::quat liftRotation; + swingTwistDecomposition(getHead()->getCameraOrientation(), _worldUpDirection, liftRotation, motorRotation); } const float DEFAULT_MOTOR_TIMESCALE = 0.2f; const float INVALID_MOTOR_TIMESCALE = 1.0e6f; @@ -1216,8 +1248,7 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setPositionAndOrientation(getPosition(), getOrientation()); if (qApp->isHMDMode()) { - bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; - _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput); + _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { _follow.deactivate(); } @@ -1226,13 +1257,13 @@ void MyAvatar::prepareForPhysicsSimulation() { void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { glm::vec3 position = getPosition(); glm::quat orientation = getOrientation(); - if (_characterController.isEnabled()) { + if (_characterController.isEnabledAndReady()) { _characterController.getPositionAndOrientation(position, orientation); } nextAttitude(position, orientation); _bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix); - if (_characterController.isEnabled()) { + if (_characterController.isEnabledAndReady()) { setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); } else { setVelocity(getVelocity() + _characterController.getFollowVelocity()); @@ -1615,7 +1646,7 @@ void MyAvatar::updatePosition(float deltaTime) { vec3 velocity = getVelocity(); const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s - if (!_characterController.isEnabled()) { + if (!_characterController.isEnabledAndReady()) { // _characterController is not in physics simulation but it can still compute its target velocity updateMotors(); _characterController.computeNewVelocity(deltaTime, velocity); @@ -1788,7 +1819,30 @@ void MyAvatar::updateMotionBehaviorFromMenu() { } else { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } - _characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController)); + + setCharacterControllerEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController)); +} + +void MyAvatar::setCharacterControllerEnabled(bool enabled) { + + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setCharacterControllerEnabled", Q_ARG(bool, enabled)); + return; + } + + bool ghostingAllowed = true; + EntityTreeRenderer* entityTreeRenderer = qApp->getEntities(); + if (entityTreeRenderer) { + std::shared_ptr zone = entityTreeRenderer->myAvatarZone(); + if (zone) { + ghostingAllowed = zone->getGhostingAllowed(); + } + } + _characterController.setEnabled(ghostingAllowed ? enabled : true); +} + +bool MyAvatar::getCharacterControllerEnabled() { + return _characterController.isEnabled(); } void MyAvatar::clearDriveKeys() { @@ -1996,14 +2050,17 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { _desiredBodyMatrix = desiredBodyMatrix; - if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { - activate(Rotation); - } - if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { - activate(Horizontal); - } - if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(Vertical); + + if (myAvatar.getHMDLeanRecenterEnabled()) { + if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { + activate(Rotation); + } + if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { + activate(Horizontal); + } + if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { + activate(Vertical); + } } glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix; @@ -2076,3 +2133,7 @@ bool MyAvatar::didTeleport() { lastPosition = pos; return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME); } + +bool MyAvatar::hasDriveInput() const { + return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1b335fe294..a21922f1b1 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -69,7 +69,6 @@ class MyAvatar : public Avatar { Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom) //TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity) - Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition) Q_PROPERTY(glm::vec3 rightHandPosition READ getRightHandPosition) Q_PROPERTY(glm::vec3 leftHandTipPosition READ getLeftHandTipPosition) @@ -84,6 +83,9 @@ class MyAvatar : public Avatar { Q_PROPERTY(float energy READ getEnergy WRITE setEnergy) + Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled) + Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) + public: explicit MyAvatar(RigPointer rig); ~MyAvatar(); @@ -94,7 +96,7 @@ public: AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; } AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; } - Q_INVOKABLE void reset(bool andRecenter = false); + Q_INVOKABLE void reset(bool andRecenter = false, bool andReload = true, bool andHead = true); void update(float deltaTime); virtual void postUpdate(float deltaTime) override; void preDisplaySide(RenderArgs* renderArgs); @@ -123,9 +125,6 @@ public: void setRealWorldFieldOfView(float realWorldFov) { _realWorldFieldOfView.set(realWorldFov); } - void setLeanScale(float scale) { _leanScale = scale; } - float getLeanScale() const { return _leanScale; } - Q_INVOKABLE glm::vec3 getDefaultEyePosition() const; float getRealWorldFieldOfView() { return _realWorldFieldOfView.get(); } @@ -160,6 +159,11 @@ public: Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; } Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } + Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; } + Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; } + + Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; } + Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; } // get/set avatar data void saveData(); @@ -208,8 +212,8 @@ public: virtual void clearJointsData() override; Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString()); - Q_INVOKABLE const QUrl& getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; } - Q_INVOKABLE const QString& getFullAvatarModelName() const { return _fullAvatarModelName; } + Q_INVOKABLE QUrl getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; } + Q_INVOKABLE QString getFullAvatarModelName() const { return _fullAvatarModelName; } void resetFullAvatarURL(); @@ -262,6 +266,11 @@ public: controller::Pose getLeftHandControllerPoseInAvatarFrame() const; controller::Pose getRightHandControllerPoseInAvatarFrame() const; + bool hasDriveInput() const; + + Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); + Q_INVOKABLE bool getCharacterControllerEnabled(); + public slots: void increaseSize(); void decreaseSize(); @@ -396,6 +405,7 @@ private: QString _fullAvatarModelName; QUrl _animGraphUrl {""}; bool _useSnapTurn { true }; + bool _clearOverlayWhenMoving { true }; // cache of the current HMD sensor position and orientation // in sensor space. @@ -467,6 +477,8 @@ private: ThreadSafeValueCache _leftHandControllerPoseInSensorFrameCache { controller::Pose() }; ThreadSafeValueCache _rightHandControllerPoseInSensorFrameCache { controller::Pose() }; + bool _hmdLeanRecenterEnabled = true; + float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f }; float AUDIO_ENERGY_CONSTANT { 0.000001f }; float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f }; diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 5deeb545a1..889f0ef36b 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -106,10 +106,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { MyAvatar* myAvatar = static_cast(_owningAvatar); Rig::HeadParameters headParams; - headParams.enableLean = qApp->isHMDMode(); - headParams.leanSideways = head->getFinalLeanSideways(); - headParams.leanForward = head->getFinalLeanForward(); - headParams.torsoTwist = head->getTorsoTwist(); if (qApp->isHMDMode()) { headParams.isInHMD = true; @@ -131,7 +127,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame(); } - headParams.leanJointIndex = geometry.leanJointIndex; headParams.neckJointIndex = geometry.neckJointIndex; headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f; diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 1726d47045..8fc0384aee 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -46,6 +46,12 @@ int main(int argc, const char* argv[]) { bool instanceMightBeRunning = true; + QStringList arguments; + for (int i = 0; i < argc; ++i) { + arguments << argv[i]; + } + + #ifdef Q_OS_WIN // Try to create a shared memory block - if it can't be created, there is an instance of // interface already running. We only do this on Windows for now because of the potential @@ -64,12 +70,6 @@ int main(int argc, const char* argv[]) { // Try to connect - if we can't connect, interface has probably just gone down if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) { - - QStringList arguments; - for (int i = 0; i < argc; ++i) { - arguments << argv[i]; - } - QCommandLineParser parser; QCommandLineOption urlOption("url", "", "value"); parser.addOption(urlOption); @@ -135,7 +135,7 @@ int main(int argc, const char* argv[]) { // Oculus initialization MUST PRECEDE OpenGL context creation. // The nature of the Application constructor means this has to be either here, // or in the main window ctor, before GL startup. - Application::initPlugins(); + Application::initPlugins(arguments); int exitCode; { @@ -144,11 +144,11 @@ int main(int argc, const char* argv[]) { // If we failed the OpenGLVersion check, log it. if (override) { - auto& accountManager = AccountManager::getInstance(); - if (accountManager.isLoggedIn()) { + auto accountManager = DependencyManager::get(); + if (accountManager->isLoggedIn()) { UserActivityLogger::getInstance().insufficientGLVersion(glData); } else { - QObject::connect(&AccountManager::getInstance(), &AccountManager::loginComplete, [glData](){ + QObject::connect(accountManager.data(), &AccountManager::loginComplete, [glData](){ static bool loggedInsufficientGL = false; if (!loggedInsufficientGL) { UserActivityLogger::getInstance().insufficientGLVersion(glData); @@ -168,9 +168,9 @@ int main(int argc, const char* argv[]) { QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection, Qt::DirectConnection); #ifdef HAS_BUGSPLAT - AccountManager& accountManager = AccountManager::getInstance(); - crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager.getAccountInfo().getUsername())); - QObject::connect(&accountManager, &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) { + auto accountManager = DependencyManager::get(); + crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager->getAccountInfo().getUsername())); + QObject::connect(accountManager.data(), &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) { crashReporter.mpSender.setDefaultUserName(qPrintable(newUsername)); }); diff --git a/interface/src/scripting/AccountScriptingInterface.cpp b/interface/src/scripting/AccountScriptingInterface.cpp index 1b6d52ac2a..1328197195 100644 --- a/interface/src/scripting/AccountScriptingInterface.cpp +++ b/interface/src/scripting/AccountScriptingInterface.cpp @@ -19,14 +19,14 @@ AccountScriptingInterface* AccountScriptingInterface::getInstance() { } bool AccountScriptingInterface::isLoggedIn() { - AccountManager& accountManager = AccountManager::getInstance(); - return accountManager.isLoggedIn(); + auto accountManager = DependencyManager::get(); + return accountManager->isLoggedIn(); } QString AccountScriptingInterface::getUsername() { - AccountManager& accountManager = AccountManager::getInstance(); - if (accountManager.isLoggedIn()) { - return accountManager.getAccountInfo().getUsername(); + auto accountManager = DependencyManager::get(); + if (accountManager->isLoggedIn()) { + return accountManager->getAccountInfo().getUsername(); } else { return "Unknown user"; } diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 965b3a9e0c..f1198c9d5b 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -196,7 +196,7 @@ bool AssetMappingModel::isKnownFolder(QString path) const { return false; } -static int assetMappingModelMetatypeId = qRegisterMetaType("AssetMappingModel*"); +int assetMappingModelMetatypeId = qRegisterMetaType("AssetMappingModel*"); void AssetMappingModel::refresh() { qDebug() << "Refreshing asset mapping model"; diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index b0ef6c760d..b803080538 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -14,6 +14,10 @@ ClipboardScriptingInterface::ClipboardScriptingInterface() { } +glm::vec3 ClipboardScriptingInterface::getContentsDimensions() { + return qApp->getEntityClipboard()->getContentsDimensions(); +} + float ClipboardScriptingInterface::getClipboardContentsLargestDimension() { return qApp->getEntityClipboard()->getContentsLargestDimension(); } diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index 73bebd4836..4737a194df 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -22,6 +22,7 @@ signals: void readyToImport(); public slots: + glm::vec3 getContentsDimensions(); /// returns the overall dimensions of everything on the blipboard float getClipboardContentsLargestDimension(); /// returns the largest dimension of everything on the clipboard bool importEntities(const QString& filename); bool exportEntities(const QString& filename, const QVector& entityIDs); diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index 843a40348e..f7bc8afe36 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -16,6 +16,7 @@ #include "Application.h" #include "MainWindow.h" +#include int DesktopScriptingInterface::getWidth() { QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize(); @@ -25,3 +26,8 @@ int DesktopScriptingInterface::getHeight() { QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize(); return size.height(); } + +void DesktopScriptingInterface::setOverlayAlpha(float alpha) { + qApp->getApplicationCompositor().setAlpha(alpha); +} + diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index be4eaadbfb..8da502cb11 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -22,6 +22,8 @@ class DesktopScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus public: + Q_INVOKABLE void setOverlayAlpha(float alpha); + int getWidth(); int getHeight(); }; diff --git a/interface/src/scripting/DialogsManagerScriptingInterface.cpp b/interface/src/scripting/DialogsManagerScriptingInterface.cpp index 80a8b4ac7c..cbca7ff4ff 100644 --- a/interface/src/scripting/DialogsManagerScriptingInterface.cpp +++ b/interface/src/scripting/DialogsManagerScriptingInterface.cpp @@ -18,6 +18,8 @@ DialogsManagerScriptingInterface::DialogsManagerScriptingInterface() { connect(DependencyManager::get().data(), &DialogsManager::addressBarToggled, this, &DialogsManagerScriptingInterface::addressBarToggled); + connect(DependencyManager::get().data(), &DialogsManager::addressBarShown, + this, &DialogsManagerScriptingInterface::addressBarShown); } void DialogsManagerScriptingInterface::toggleAddressBar() { diff --git a/interface/src/scripting/DialogsManagerScriptingInterface.h b/interface/src/scripting/DialogsManagerScriptingInterface.h index ef44e20d61..075b89f0e5 100644 --- a/interface/src/scripting/DialogsManagerScriptingInterface.h +++ b/interface/src/scripting/DialogsManagerScriptingInterface.h @@ -24,6 +24,7 @@ public slots: signals: void addressBarToggled(); + void addressBarShown(bool visible); }; #endif diff --git a/interface/src/scripting/GlobalServicesScriptingInterface.cpp b/interface/src/scripting/GlobalServicesScriptingInterface.cpp index e8d63a6d99..d7e5bae3f8 100644 --- a/interface/src/scripting/GlobalServicesScriptingInterface.cpp +++ b/interface/src/scripting/GlobalServicesScriptingInterface.cpp @@ -17,10 +17,10 @@ #include "GlobalServicesScriptingInterface.h" GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() { - AccountManager& accountManager = AccountManager::getInstance(); - connect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); - connect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); - connect(&accountManager, &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); + connect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); + connect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); _downloading = false; QTimer* checkDownloadTimer = new QTimer(this); @@ -34,10 +34,10 @@ GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() { } GlobalServicesScriptingInterface::~GlobalServicesScriptingInterface() { - AccountManager& accountManager = AccountManager::getInstance(); - disconnect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); - disconnect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); - disconnect(&accountManager, &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); + auto accountManager = DependencyManager::get(); + disconnect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); + disconnect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); + disconnect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); } GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance() { @@ -46,7 +46,7 @@ GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance( } const QString& GlobalServicesScriptingInterface::getUsername() const { - return AccountManager::getInstance().getAccountInfo().getUsername(); + return DependencyManager::get()->getAccountInfo().getUsername(); } void GlobalServicesScriptingInterface::loggedOut() { diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 7bf1547a3c..36cde378f8 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -15,10 +15,14 @@ #include #include +#include #include #include "Application.h" HMDScriptingInterface::HMDScriptingInterface() { + connect(qApp, &Application::activeDisplayPluginChanged, [this]{ + emit displayModeChanged(isHMDMode()); + }); } glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const { @@ -105,3 +109,29 @@ QString HMDScriptingInterface::preferredAudioInput() const { QString HMDScriptingInterface::preferredAudioOutput() const { return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice(); } + +bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const { + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([offscreenUi, enabled] { + offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled); + }); + return qApp->getActiveDisplayPlugin()->setHandLaser(hands, + enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None, + color, direction); +} + +void HMDScriptingInterface::disableHandLasers(int hands) const { + setHandLasers(hands, false, vec4(0), vec3(0)); +} + +bool HMDScriptingInterface::suppressKeyboard() { + return qApp->getActiveDisplayPlugin()->suppressKeyboard(); +} + +void HMDScriptingInterface::unsuppressKeyboard() { + qApp->getActiveDisplayPlugin()->unsuppressKeyboard(); +} + +bool HMDScriptingInterface::isKeyboardVisible() { + return qApp->getActiveDisplayPlugin()->isKeyboardVisible(); +} diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index d4c7b7cc0e..2739522adf 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -1,4 +1,4 @@ -// + // HMDScriptingInterface.h // interface/src/scripting // @@ -12,6 +12,8 @@ #ifndef hifi_HMDScriptingInterface_h #define hifi_HMDScriptingInterface_h +#include + #include class QScriptContext; class QScriptEngine; @@ -31,12 +33,28 @@ public: Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const; Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec3& position) const; Q_INVOKABLE glm::vec3 worldPointFromOverlay(const glm::vec2& overlay) const; - Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const; Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const; Q_INVOKABLE QString preferredAudioInput() const; Q_INVOKABLE QString preferredAudioOutput() const; + Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const; + + Q_INVOKABLE void disableHandLasers(int hands) const; + /// Suppress the activation of any on-screen keyboard so that a script operation will + /// not be interrupted by a keyboard popup + /// Returns false if there is already an active keyboard displayed. + /// Clients should re-enable the keyboard when the operation is complete and ensure + /// that they balance any call to suppressKeyboard() that returns true with a corresponding + /// call to unsuppressKeyboard() within a reasonable amount of time + Q_INVOKABLE bool suppressKeyboard(); + + /// Enable the keyboard following a suppressKeyboard call + Q_INVOKABLE void unsuppressKeyboard(); + + /// Query the display plugin to determine the current VR keyboard visibility + Q_INVOKABLE bool isKeyboardVisible(); + public: HMDScriptingInterface(); static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine); diff --git a/interface/src/scripting/ToolbarScriptingInterface.cpp b/interface/src/scripting/ToolbarScriptingInterface.cpp new file mode 100644 index 0000000000..0cb314615a --- /dev/null +++ b/interface/src/scripting/ToolbarScriptingInterface.cpp @@ -0,0 +1,129 @@ +// +// Created by Bradley Austin Davis on 2016-06-16 +// Copyright 2013-2016 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 "ToolbarScriptingInterface.h" + +#include + +#include + +class QmlWrapper : public QObject { + Q_OBJECT +public: + QmlWrapper(QObject* qmlObject, QObject* parent = nullptr) + : QObject(parent), _qmlObject(qmlObject) { + } + + Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=] { + _qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue); + }); + } + + Q_INVOKABLE void writeProperties(QVariant propertyMap) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=] { + QVariantMap map = propertyMap.toMap(); + for (const QString& key : map.keys()) { + _qmlObject->setProperty(key.toStdString().c_str(), map[key]); + } + }); + } + + Q_INVOKABLE QVariant readProperty(const QString& propertyName) { + auto offscreenUi = DependencyManager::get(); + return offscreenUi->returnFromUiThread([&]()->QVariant { + return _qmlObject->property(propertyName.toStdString().c_str()); + }); + } + + Q_INVOKABLE QVariant readProperties(const QVariant& propertyList) { + auto offscreenUi = DependencyManager::get(); + return offscreenUi->returnFromUiThread([&]()->QVariant { + QVariantMap result; + for (const QVariant& property : propertyList.toList()) { + QString propertyString = property.toString(); + result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str())); + } + return result; + }); + } + + +protected: + QObject* _qmlObject{ nullptr }; +}; + + +class ToolbarButtonProxy : public QmlWrapper { + Q_OBJECT + +public: + ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) { + connect(qmlObject, SIGNAL(clicked()), this, SIGNAL(clicked())); + } + +signals: + void clicked(); +}; + +class ToolbarProxy : public QmlWrapper { + Q_OBJECT + +public: + ToolbarProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) { } + + Q_INVOKABLE QObject* addButton(const QVariant& properties) { + QVariant resultVar; + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != _qmlObject->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", connectionType, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties)); + if (!invokeResult) { + return nullptr; + } + + QObject* rawButton = qvariant_cast(resultVar); + if (!rawButton) { + return nullptr; + } + + return new ToolbarButtonProxy(rawButton, this); + } + + Q_INVOKABLE void removeButton(const QVariant& name) { + QMetaObject::invokeMethod(_qmlObject, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, name)); + } +}; + + +QObject* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) { + auto offscreenUi = DependencyManager::get(); + auto desktop = offscreenUi->getDesktop(); + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != desktop->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + QVariant resultVar; + bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", connectionType, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId)); + if (!invokeResult) { + return nullptr; + } + + QObject* rawToolbar = qvariant_cast(resultVar); + if (!rawToolbar) { + return nullptr; + } + + return new ToolbarProxy(rawToolbar); +} + + +#include "ToolbarScriptingInterface.moc" diff --git a/interface/src/scripting/ToolbarScriptingInterface.h b/interface/src/scripting/ToolbarScriptingInterface.h new file mode 100644 index 0000000000..9379284e55 --- /dev/null +++ b/interface/src/scripting/ToolbarScriptingInterface.h @@ -0,0 +1,26 @@ +// +// Created by Bradley Austin Davis on 2016-06-16 +// Copyright 2013-2016 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_ToolbarScriptingInterface_h +#define hifi_ToolbarScriptingInterface_h + +#include + +#include + +#include + +class ToolbarProxy; + +class ToolbarScriptingInterface : public QObject, public Dependency { + Q_OBJECT +public: + Q_INVOKABLE QObject* getToolbar(const QString& toolbarId); +}; + +#endif // hifi_ToolbarScriptingInterface_h diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 0443c65453..f0ae221566 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -14,6 +14,8 @@ #include #include +#include + #include "Application.h" #include "DomainHandler.h" #include "MainWindow.h" @@ -23,6 +25,10 @@ #include "WindowScriptingInterface.h" +static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); +static const QString LAST_BROWSE_LOCATION_SETTING = "LastBrowseLocation"; + + WindowScriptingInterface::WindowScriptingInterface() { const DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged); @@ -101,6 +107,14 @@ QString fixupPathForMac(const QString& directory) { return path; } +QString WindowScriptingInterface::getPreviousBrowseLocation() const { + return Setting::Handle(LAST_BROWSE_LOCATION_SETTING, DESKTOP_LOCATION).get(); +} + +void WindowScriptingInterface::setPreviousBrowseLocation(const QString& location) { + Setting::Handle(LAST_BROWSE_LOCATION_SETTING).set(location); +} + /// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current /// working directory. /// \param const QString& title title of the window @@ -108,8 +122,17 @@ QString fixupPathForMac(const QString& directory) { /// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` /// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` QScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) { - QString path = fixupPathForMac(directory); + QString path = directory; + if (path.isEmpty()) { + path = getPreviousBrowseLocation(); + } +#ifndef Q_OS_WIN + path = fixupPathForMac(directory); +#endif QString result = OffscreenUi::getOpenFileName(nullptr, title, path, nameFilter); + if (!result.isEmpty()) { + setPreviousBrowseLocation(QFileInfo(result).absolutePath()); + } return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); } @@ -120,8 +143,17 @@ QScriptValue WindowScriptingInterface::browse(const QString& title, const QStrin /// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` /// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` QScriptValue WindowScriptingInterface::save(const QString& title, const QString& directory, const QString& nameFilter) { - QString path = fixupPathForMac(directory); + QString path = directory; + if (path.isEmpty()) { + path = getPreviousBrowseLocation(); + } +#ifndef Q_OS_WIN + path = fixupPathForMac(directory); +#endif QString result = OffscreenUi::getSaveFileName(nullptr, title, path, nameFilter); + if (!result.isEmpty()) { + setPreviousBrowseLocation(QFileInfo(result).absolutePath()); + } return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 0f51a484c4..4f26ccd057 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -45,10 +45,15 @@ public slots: signals: void domainChanged(const QString& domainHostname); void svoImportRequested(const QString& url); - void domainConnectionRefused(const QString& reason); + void domainConnectionRefused(const QString& reasonMessage, int reasonCode); + void snapshotTaken(const QString& path); private slots: WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); + +private: + QString getPreviousBrowseLocation() const; + void setPreviousBrowseLocation(const QString& location); }; #endif // hifi_WindowScriptingInterface_h diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index ba0cf18d32..a4ef8a913f 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -16,6 +16,7 @@ #include "DependencyManager.h" #include "AddressManager.h" +#include "DialogsManager.h" HIFI_QML_DEF(AddressBarDialog) @@ -39,10 +40,10 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare _forwardEnabled = !(DependencyManager::get()->getForwardStack().isEmpty()); } -void AddressBarDialog::loadAddress(const QString& address) { +void AddressBarDialog::loadAddress(const QString& address, bool fromSuggestions) { qDebug() << "Called LoadAddress with address " << address; if (!address.isEmpty()) { - DependencyManager::get()->handleLookupString(address); + DependencyManager::get()->handleLookupString(address, fromSuggestions); } } @@ -74,3 +75,6 @@ void AddressBarDialog::displayAddressNotFoundMessage() { OffscreenUi::critical("", "There is no address information for that user or place"); } +void AddressBarDialog::observeShownChanged(bool visible) { + DependencyManager::get()->emitAddressBarShown(visible); +} diff --git a/interface/src/ui/AddressBarDialog.h b/interface/src/ui/AddressBarDialog.h index b2751860cc..6c7620164b 100644 --- a/interface/src/ui/AddressBarDialog.h +++ b/interface/src/ui/AddressBarDialog.h @@ -34,10 +34,11 @@ protected: void displayAddressOfflineMessage(); void displayAddressNotFoundMessage(); - Q_INVOKABLE void loadAddress(const QString& address); + Q_INVOKABLE void loadAddress(const QString& address, bool fromSuggestions = false); Q_INVOKABLE void loadHome(); Q_INVOKABLE void loadBack(); Q_INVOKABLE void loadForward(); + Q_INVOKABLE void observeShownChanged(bool visible); bool _backEnabled; bool _forwardEnabled; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 2395f62468..6d5df31766 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -150,7 +150,8 @@ void ApplicationOverlay::renderRearViewToFbo(RenderArgs* renderArgs) { } void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) { - if (!qApp->isHMDMode() && Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) { + if (!qApp->isHMDMode() && Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror) && + !Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { gpu::Batch& batch = *renderArgs->_batch; auto geometryCache = DependencyManager::get(); @@ -166,7 +167,7 @@ void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) { batch.setViewTransform(Transform()); float screenRatio = ((float)qApp->getDevicePixelRatio()); - float renderRatio = ((float)screenRatio * qApp->getRenderResolutionScale()); + float renderRatio = ((float)qApp->getRenderResolutionScale()); auto viewport = qApp->getMirrorViewRect(); glm::vec2 bottomLeft(viewport.left(), viewport.top() + viewport.height()); diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index b8fa22ec83..c48c6df0e6 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -40,6 +40,7 @@ public: QPointer getHMDToolsDialog() const { return _hmdToolsDialog; } QPointer getLodToolsDialog() const { return _lodToolsDialog; } QPointer getOctreeStatsDialog() const { return _octreeStatsDialog; } + void emitAddressBarShown(bool visible) { emit addressBarShown(visible); } public slots: void toggleAddressBar(); @@ -60,6 +61,7 @@ public slots: signals: void addressBarToggled(); + void addressBarShown(bool visible); private slots: void hmdToolsClosed(); diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 60d19df355..80d52b7a07 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -24,14 +24,15 @@ HIFI_QML_DEF(LoginDialog) LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent), _rootUrl(NetworkingConstants::METAVERSE_SERVER_URL.toString()) { - connect(&AccountManager::getInstance(), &AccountManager::loginComplete, + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::loginComplete, this, &LoginDialog::handleLoginCompleted); - connect(&AccountManager::getInstance(), &AccountManager::loginFailed, + connect(accountManager.data(), &AccountManager::loginFailed, this, &LoginDialog::handleLoginFailed); } void LoginDialog::toggleAction() { - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); QAction* loginAction = Menu::getInstance()->getActionForOption(MenuOption::Login); Q_CHECK_PTR(loginAction); static QMetaObject::Connection connection; @@ -39,10 +40,10 @@ void LoginDialog::toggleAction() { disconnect(connection); } - if (accountManager.isLoggedIn()) { + if (accountManager->isLoggedIn()) { // change the menu item to logout - loginAction->setText("Logout " + accountManager.getAccountInfo().getUsername()); - connection = connect(loginAction, &QAction::triggered, &accountManager, &AccountManager::logout); + loginAction->setText("Logout " + accountManager->getAccountInfo().getUsername()); + connection = connect(loginAction, &QAction::triggered, accountManager.data(), &AccountManager::logout); } else { // change the menu item to login loginAction->setText("Login"); @@ -78,7 +79,7 @@ QString LoginDialog::rootUrl() const { void LoginDialog::login(const QString& username, const QString& password) { qDebug() << "Attempting to login " << username; setStatusText("Logging in..."); - AccountManager::getInstance().requestAccessToken(username, password); + DependencyManager::get()->requestAccessToken(username, password); } void LoginDialog::openUrl(const QString& url) { diff --git a/interface/src/ui/OctreeStatsDialog.cpp b/interface/src/ui/OctreeStatsDialog.cpp index 6b6d600e20..fc0e6781d7 100644 --- a/interface/src/ui/OctreeStatsDialog.cpp +++ b/interface/src/ui/OctreeStatsDialog.cpp @@ -433,13 +433,13 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser } const JurisdictionMap& map = serverJurisdictions[nodeUUID]; - unsigned char* rootCode = map.getRootOctalCode(); + auto rootCode = map.getRootOctalCode(); if (rootCode) { - QString rootCodeHex = octalCodeToHexString(rootCode); + QString rootCodeHex = octalCodeToHexString(rootCode.get()); VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); serverDetails << " jurisdiction: " << qPrintable(rootCodeHex) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 95054869e5..2ee106b6b3 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -17,139 +17,137 @@ #include "OverlayConductor.h" OverlayConductor::OverlayConductor() { + } OverlayConductor::~OverlayConductor() { } +bool OverlayConductor::headOutsideOverlay() const { + glm::mat4 hmdMat = qApp->getHMDSensorPose(); + glm::vec3 hmdPos = extractTranslation(hmdMat); + glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f)); + + Transform uiTransform = qApp->getApplicationCompositor().getModelTransform(); + glm::vec3 uiPos = uiTransform.getTranslation(); + glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); + + const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface. + const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled + if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE || + glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) { + return true; + } + return false; +} + +bool OverlayConductor::updateAvatarIsAtRest() { + + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s + const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms + + const float AT_REST_THRESHOLD = 0.01f; + bool desiredAtRest = glm::length(myAvatar->getVelocity()) < AT_REST_THRESHOLD; + if (desiredAtRest != _desiredAtRest) { + // start timer + _desiredAtRestTimer = usecTimestampNow() + (desiredAtRest ? REST_ENABLE_TIME_USECS : REST_DISABLE_TIME_USECS); + } + + _desiredAtRest = desiredAtRest; + + if (_desiredAtRestTimer != 0 && usecTimestampNow() > _desiredAtRestTimer) { + // timer expired + // change state! + _currentAtRest = _desiredAtRest; + // disable timer + _desiredAtRestTimer = 0; + } + + return _currentAtRest; +} + +bool OverlayConductor::updateAvatarHasDriveInput() { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms + const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s + + bool desiredDriving = myAvatar->hasDriveInput(); + if (desiredDriving != _desiredDriving) { + // start timer + _desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS); + } + + _desiredDriving = desiredDriving; + + if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) { + // timer expired + // change state! + _currentDriving = _desiredDriving; + // disable timer + _desiredDrivingTimer = 0; + } + + return _currentDriving; +} + +void OverlayConductor::centerUI() { + // place the overlay at the current hmd position in sensor space + auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose()); + qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); +} + void OverlayConductor::update(float dt) { + auto offscreenUi = DependencyManager::get(); + bool currentVisible = !offscreenUi->getDesktop()->property("pinned").toBool(); - updateMode(); - - switch (_mode) { - case SITTING: { - // when sitting, the overlay is at the origin, facing down the -z axis. - // the camera is taken directly from the HMD. - Transform identity; - qApp->getApplicationCompositor().setModelTransform(identity); - qApp->getApplicationCompositor().setCameraBaseTransform(identity); - break; - } - case STANDING: { - // when standing, the overlay is at a reference position, which is set when the overlay is - // enabled. The camera is taken directly from the HMD, but in world space. - // So the sensorToWorldMatrix must be applied. - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - Transform t; - t.evalFromRawMatrix(myAvatar->getSensorToWorldMatrix()); - qApp->getApplicationCompositor().setCameraBaseTransform(t); - - // detect when head moves out side of sweet spot, or looks away. - mat4 headMat = myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose(); - vec3 headWorldPos = extractTranslation(headMat); - vec3 headForward = glm::quat_cast(headMat) * glm::vec3(0.0f, 0.0f, -1.0f); - Transform modelXform = qApp->getApplicationCompositor().getModelTransform(); - vec3 compositorWorldPos = modelXform.getTranslation(); - vec3 compositorForward = modelXform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); - const float MAX_COMPOSITOR_DISTANCE = 0.6f; - const float MAX_COMPOSITOR_ANGLE = 110.0f; - if (_enabled && (glm::distance(headWorldPos, compositorWorldPos) > MAX_COMPOSITOR_DISTANCE || - glm::dot(headForward, compositorForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE)))) { - // fade out the overlay - setEnabled(false); + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + // centerUI when hmd mode is first enabled and mounted + if (qApp->isHMDMode() && qApp->getActiveDisplayPlugin()->isDisplayVisible()) { + if (!_hmdMode) { + _hmdMode = true; + centerUI(); } - break; - } - case FLAT: - // do nothing - break; - } -} - -void OverlayConductor::updateMode() { - - Mode newMode; - if (qApp->isHMDMode()) { - newMode = SITTING; } else { - newMode = FLAT; + _hmdMode = false; } - if (newMode != _mode) { - switch (newMode) { - case SITTING: { - // enter the SITTING state - // place the overlay at origin - Transform identity; - qApp->getApplicationCompositor().setModelTransform(identity); - break; - } - case STANDING: { - // enter the STANDING state - // place the overlay at the current hmd position in world space - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - Transform t; - t.setTranslation(extractTranslation(camMat)); - t.setRotation(glm::quat_cast(camMat)); - qApp->getApplicationCompositor().setModelTransform(t); - break; - } + bool prevDriving = _currentDriving; + bool isDriving = updateAvatarHasDriveInput(); + bool drivingChanged = prevDriving != isDriving; + bool isAtRest = updateAvatarIsAtRest(); + bool shouldRecenter = false; - case FLAT: - // do nothing - break; + if (_flags & SuppressedByDrive) { + if (!isDriving) { + _flags &= ~SuppressedByDrive; + shouldRecenter = true; + } + } else { + if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { + _flags |= SuppressedByDrive; } } - _mode = newMode; -} - -void OverlayConductor::setEnabled(bool enabled) { - - if (enabled == _enabled) { - return; + if (_flags & SuppressedByHead) { + if (isAtRest) { + _flags &= ~SuppressedByHead; + shouldRecenter = true; + } + } else { + if (_hmdMode && headOutsideOverlay()) { + _flags |= SuppressedByHead; + } } - Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled); - _enabled = enabled; // set the new value - - // if the new state is visible/enabled... - if (_enabled) { - // alpha fadeIn the overlay mesh. - qApp->getApplicationCompositor().fadeIn(); - - // enable mouse clicks from script - qApp->getOverlays().enable(); - - // enable QML events - auto offscreenUi = DependencyManager::get(); - offscreenUi->getRootItem()->setEnabled(true); - - if (_mode == STANDING) { - // place the overlay at the current hmd position in world space - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - Transform t; - t.setTranslation(extractTranslation(camMat)); - t.setRotation(glm::quat_cast(camMat)); - qApp->getApplicationCompositor().setModelTransform(t); - } - } else { // other wise, if the new state is hidden/not enabled - // alpha fadeOut the overlay mesh. - qApp->getApplicationCompositor().fadeOut(); - - // disable mouse clicks from script - qApp->getOverlays().disable(); - - // disable QML events - auto offscreenUi = DependencyManager::get(); - offscreenUi->getRootItem()->setEnabled(false); + bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask)); + if (targetVisible != currentVisible) { + offscreenUi->setPinned(!targetVisible); + } + if (shouldRecenter && !_flags) { + centerUI(); } } - -bool OverlayConductor::getEnabled() const { - return _enabled; -} - diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index b94c5be7dd..1bdfe2ed79 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -17,20 +17,31 @@ public: ~OverlayConductor(); void update(float dt); - void setEnabled(bool enable); - bool getEnabled() const; + void centerUI(); private: - void updateMode(); + bool headOutsideOverlay() const; + bool updateAvatarHasDriveInput(); + bool updateAvatarIsAtRest(); - enum Mode { - FLAT, - SITTING, - STANDING + enum SupressionFlags { + SuppressedByDrive = 0x01, + SuppressedByHead = 0x02, + SuppressMask = 0x03, }; - Mode _mode { FLAT }; - bool _enabled { false }; + uint8_t _flags { SuppressedByDrive }; + bool _hmdMode { false }; + + // used by updateAvatarHasDriveInput + quint64 _desiredDrivingTimer { 0 }; + bool _desiredDriving { false }; + bool _currentDriving { false }; + + // used by updateAvatarIsAtRest + quint64 _desiredAtRestTimer { 0 }; + bool _desiredAtRest { true }; + bool _currentAtRest { true }; }; #endif diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 9b1146340e..4007c940c3 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -35,7 +35,7 @@ void setupPreferences() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); static const QString AVATAR_BASICS { "Avatar Basics" }; { - auto getter = [=]()->QString {return myAvatar->getDisplayName(); }; + auto getter = [=]()->QString { return myAvatar->getDisplayName(); }; auto setter = [=](const QString& value) { myAvatar->setDisplayName(value); }; auto preference = new EditPreference(AVATAR_BASICS, "Avatar display name (optional)", getter, setter); preference->setPlaceholderText("Not showing a name"); @@ -43,7 +43,7 @@ void setupPreferences() { } { - auto getter = [=]()->QString {return myAvatar->getCollisionSoundURL(); }; + auto getter = [=]()->QString { return myAvatar->getCollisionSoundURL(); }; auto setter = [=](const QString& value) { myAvatar->setCollisionSoundURL(value); }; auto preference = new EditPreference(AVATAR_BASICS, "Avatar collision sound URL (optional)", getter, setter); preference->setPlaceholderText("Enter the URL of a sound to play when you bump into something"); @@ -56,15 +56,24 @@ void setupPreferences() { auto preference = new AvatarPreference(AVATAR_BASICS, "Appearance", getter, setter); preferences->addPreference(preference); } + { - auto getter = [=]()->bool {return myAvatar->getSnapTurn(); }; + auto getter = [=]()->bool { return myAvatar->getSnapTurn(); }; auto setter = [=](bool value) { myAvatar->setSnapTurn(value); }; preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter)); } + { + auto getter = [=]()->bool { return myAvatar->getClearOverlayWhenMoving(); }; + auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); }; + preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter)); + } + + // Snapshots + static const QString SNAPSHOTS { "Snapshots" }; { auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); }; auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); }; - auto preference = new BrowsePreference("Snapshots", "Put my snapshots here", getter, setter); + auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter); preferences->addPreference(preference); } @@ -80,7 +89,7 @@ void setupPreferences() { })); { - auto getter = []()->bool {return !Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger); }; + auto getter = []()->bool { return !Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger); }; auto setter = [](bool value) { Menu::getInstance()->setIsOptionChecked(MenuOption::DisableActivityLogger, !value); }; preferences->addPreference(new CheckPreference("Privacy", "Send data", getter, setter)); } @@ -124,16 +133,6 @@ void setupPreferences() { preference->setStep(1); preferences->addPreference(preference); } - { - auto getter = [=]()->float { return myAvatar->getLeanScale(); }; - auto setter = [=](float value) { myAvatar->setLeanScale(value); }; - auto preference = new SpinnerPreference(AVATAR_TUNING, "Lean scale (applies to Faceshift users)", getter, setter); - preference->setMin(0); - preference->setMax(99.9f); - preference->setDecimals(2); - preference->setStep(1); - preferences->addPreference(preference); - } { auto getter = [=]()->float { return myAvatar->getUniformScale(); }; auto setter = [=](float value) { myAvatar->setTargetScaleVerbose(value); }; // The hell? @@ -144,11 +143,6 @@ void setupPreferences() { preference->setStep(1); preferences->addPreference(preference); } - { - auto getter = [=]()->float { return myAvatar->getHead()->getPupilDilation(); }; - auto setter = [=](float value) { myAvatar->getHead()->setPupilDilation(value); }; - preferences->addPreference(new SliderPreference(AVATAR_TUNING, "Pupil dilation", getter, setter)); - } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; auto setter = [](float value) { DependencyManager::get()->setEyeClosingThreshold(value); }; @@ -194,7 +188,7 @@ void setupPreferences() { static const QString AUDIO("Audio"); { - auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getDynamicJitterBuffers(); }; + auto getter = []()->bool { return DependencyManager::get()->getReceivedAudioStream().getDynamicJitterBuffers(); }; auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setDynamicJitterBuffers(value); }; preferences->addPreference(new CheckPreference(AUDIO, "Enable dynamic jitter buffers", getter, setter)); } @@ -217,7 +211,7 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getUseStDevForJitterCalc(); }; + auto getter = []()->bool { return DependencyManager::get()->getReceivedAudioStream().getUseStDevForJitterCalc(); }; auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setUseStDevForJitterCalc(value); }; preferences->addPreference(new CheckPreference(AUDIO, "Use standard deviation for dynamic jitter calc", getter, setter)); } @@ -246,7 +240,7 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getRepetitionWithFade(); }; + auto getter = []()->bool { return DependencyManager::get()->getReceivedAudioStream().getRepetitionWithFade(); }; auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setRepetitionWithFade(value); }; preferences->addPreference(new CheckPreference(AUDIO, "Repetition with fade", getter, setter)); } @@ -260,7 +254,7 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = []()->bool {return DependencyManager::get()->getOutputStarveDetectionEnabled(); }; + auto getter = []()->bool { return DependencyManager::get()->getOutputStarveDetectionEnabled(); }; auto setter = [](bool value) { DependencyManager::get()->setOutputStarveDetectionEnabled(value); }; auto preference = new CheckPreference(AUDIO, "Output starve detection (automatic buffer size increase)", getter, setter); preferences->addPreference(preference); diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index e35039df3d..aaf11d14a4 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -44,6 +44,7 @@ const QString URL = "highfidelity_url"; Setting::Handle Snapshot::snapshotsLocation("snapshotsLocation", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); +Setting::Handle Snapshot::hasSetSnapshotsLocation("hasSetSnapshotsLocation", false); SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) { @@ -92,7 +93,7 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) { QUrl currentURL = DependencyManager::get()->currentAddress(); shot.setText(URL, currentURL.toString()); - QString username = AccountManager::getInstance().getAccountInfo().getUsername(); + QString username = DependencyManager::get()->getAccountInfo().getUsername(); // normalize username, replace all non alphanumeric with '-' username.replace(QRegExp("[^A-Za-z0-9_]"), "-"); @@ -103,7 +104,14 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) { const int IMAGE_QUALITY = 100; if (!isTemporary) { - QString snapshotFullPath = snapshotsLocation.get(); + QString snapshotFullPath; + if (!hasSetSnapshotsLocation.get()) { + snapshotFullPath = QFileDialog::getExistingDirectory(nullptr, "Choose Snapshots Directory", snapshotsLocation.get()); + hasSetSnapshotsLocation.set(true); + snapshotsLocation.set(snapshotFullPath); + } else { + snapshotFullPath = snapshotsLocation.get(); + } if (!snapshotFullPath.endsWith(QDir::separator())) { snapshotFullPath.append(QDir::separator()); @@ -133,117 +141,3 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) { return imageTempFile; } } - -const QString FORUM_URL = "https://alphas.highfidelity.io"; -const QString FORUM_UPLOADS_URL = FORUM_URL + "/uploads"; -const QString FORUM_POST_URL = FORUM_URL + "/posts"; -const QString FORUM_REPLY_TO_TOPIC = "244"; -const QString FORUM_POST_TEMPLATE = "

%2

"; -const QString SHARE_DEFAULT_ERROR = "The server isn't responding. Please try again in a few minutes."; -const QString SUCCESS_LABEL_TEMPLATE = "Success!!! Go check out your image ...
%1"; - - -QString SnapshotUploader::uploadSnapshot(const QUrl& fileUrl) { - if (AccountManager::getInstance().getAccountInfo().getDiscourseApiKey().isEmpty()) { - OffscreenUi::warning(nullptr, "", "Your Discourse API key is missing, you cannot share snapshots. Please try to relog."); - return QString(); - } - - QHttpPart apiKeyPart; - apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"api_key\"")); - apiKeyPart.setBody(AccountManager::getInstance().getAccountInfo().getDiscourseApiKey().toLatin1()); - - QString filename = fileUrl.toLocalFile(); - qDebug() << filename; - QFile* file = new QFile(filename); - Q_ASSERT(file->exists()); - file->open(QIODevice::ReadOnly); - - QHttpPart imagePart; - imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg")); - imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"file\"; filename=\"" + file->fileName() + "\"")); - imagePart.setBodyDevice(file); - - QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart - multiPart->append(apiKeyPart); - multiPart->append(imagePart); - - QUrl url(FORUM_UPLOADS_URL); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - - QString result; - QEventLoop loop; - - QSharedPointer reply(NetworkAccessManager::getInstance().post(request, multiPart)); - QObject::connect(reply.data(), &QNetworkReply::finished, [&] { - loop.quit(); - - qDebug() << reply->errorString(); - for (const auto& header : reply->rawHeaderList()) { - qDebug() << "Header " << QString(header); - } - auto replyResult = reply->readAll(); - qDebug() << QString(replyResult); - QJsonDocument jsonResponse = QJsonDocument::fromJson(replyResult); - const QJsonObject& responseObject = jsonResponse.object(); - if (!responseObject.contains("url")) { - OffscreenUi::warning(this, "", SHARE_DEFAULT_ERROR); - return; - } - result = responseObject["url"].toString(); - }); - loop.exec(); - return result; -} - -QString SnapshotUploader::sendForumPost(const QString& snapshotPath, const QString& notes) { - // post to Discourse forum - QNetworkRequest request; - request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QUrl forumUrl(FORUM_POST_URL); - - QUrlQuery query; - query.addQueryItem("api_key", AccountManager::getInstance().getAccountInfo().getDiscourseApiKey()); - query.addQueryItem("topic_id", FORUM_REPLY_TO_TOPIC); - query.addQueryItem("raw", FORUM_POST_TEMPLATE.arg(snapshotPath, notes)); - forumUrl.setQuery(query); - - QByteArray postData = forumUrl.toEncoded(QUrl::RemoveFragment); - request.setUrl(forumUrl); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - QNetworkReply* requestReply = NetworkAccessManager::getInstance().post(request, postData); - - QEventLoop loop; - QString result; - connect(requestReply, &QNetworkReply::finished, [&] { - loop.quit(); - QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); - requestReply->deleteLater(); - const QJsonObject& responseObject = jsonResponse.object(); - - if (!responseObject.contains("id")) { - QString errorMessage(SHARE_DEFAULT_ERROR); - if (responseObject.contains("errors")) { - QJsonArray errorArray = responseObject["errors"].toArray(); - if (!errorArray.first().toString().isEmpty()) { - errorMessage = errorArray.first().toString(); - } - } - OffscreenUi::warning(this, "", errorMessage); - return; - } - - const QString urlTemplate = "%1/t/%2/%3/%4"; - result = urlTemplate.arg(FORUM_URL, - responseObject["topic_slug"].toString(), - QString::number(responseObject["topic_id"].toDouble()), - QString::number(responseObject["post_number"].toDouble())); - }); - loop.exec(); - return result; -} - diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index d87a70255f..2e7986a5c0 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -39,16 +39,9 @@ public: static SnapshotMetaData* parseSnapshotData(QString snapshotPath); static Setting::Handle snapshotsLocation; + static Setting::Handle hasSetSnapshotsLocation; private: static QFile* savedFileForSnapshot(QImage & image, bool isTemporary); }; -class SnapshotUploader : public QObject{ - Q_OBJECT -public: - SnapshotUploader(QObject* parent = nullptr) : QObject(parent) {} - Q_INVOKABLE QString uploadSnapshot(const QUrl& fileUrl); - Q_INVOKABLE QString sendForumPost(const QString& snapshotPath, const QString& notes); -}; - #endif // hifi_Snapshot_h diff --git a/interface/src/ui/SnapshotShareDialog.cpp b/interface/src/ui/SnapshotShareDialog.cpp deleted file mode 100644 index 94f89641e2..0000000000 --- a/interface/src/ui/SnapshotShareDialog.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// -// SnapshotShareDialog.cpp -// interface/src/ui -// -// Created by Stojce Slavkovski on 2/16/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 -// - -#if 0 - - -#include - -const int NARROW_SNAPSHOT_DIALOG_SIZE = 500; -const int WIDE_SNAPSHOT_DIALOG_WIDTH = 650; -const int SUCCESS_LABEL_HEIGHT = 140; - -const QString SHARE_BUTTON_STYLE = "border-width:0;border-radius:9px;border-radius:9px;font-family:Arial;font-size:18px;" - "font-weight:100;color:#FFFFFF;width: 120px;height: 50px;"; -const QString SHARE_BUTTON_ENABLED_STYLE = "background-color: #333;"; -const QString SHARE_BUTTON_DISABLED_STYLE = "background-color: #999;"; - -Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) - -SnapshotShareDialog::SnapshotShareDialog(QString fileName, QWidget* parent) : - QDialog(parent), - _fileName(fileName) -{ - - - _ui.snapshotWidget->setPixmap(snaphsotPixmap); - _ui.snapshotWidget->adjustSize(); -} - -void SnapshotShareDialog::accept() { - // prevent multiple clicks on share button - _ui.shareButton->setEnabled(false); - // gray out share button - _ui.shareButton->setStyleSheet(SHARE_BUTTON_STYLE + SHARE_BUTTON_DISABLED_STYLE); - uploadSnapshot(); -} - - -#endif diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp index 437e173807..60acd0895b 100644 --- a/interface/src/ui/UpdateDialog.cpp +++ b/interface/src/ui/UpdateDialog.cpp @@ -48,14 +48,6 @@ const QString& UpdateDialog::releaseNotes() const { return _releaseNotes; } -void UpdateDialog::closeDialog() { - hide(); -} - -void UpdateDialog::hide() { - ((QQuickItem*)parent())->setVisible(false); -} - void UpdateDialog::triggerUpgrade() { auto applicationUpdater = DependencyManager::get(); applicationUpdater.data()->performAutoUpdate(applicationUpdater.data()->getBuildData().lastKey()); diff --git a/interface/src/ui/UpdateDialog.h b/interface/src/ui/UpdateDialog.h index 84d390c942..4adff90283 100644 --- a/interface/src/ui/UpdateDialog.h +++ b/interface/src/ui/UpdateDialog.h @@ -21,22 +21,20 @@ class UpdateDialog : public OffscreenQmlDialog { Q_OBJECT HIFI_QML_DECL - Q_PROPERTY(QString updateAvailableDetails READ updateAvailableDetails) - Q_PROPERTY(QString releaseNotes READ releaseNotes) + Q_PROPERTY(QString updateAvailableDetails READ updateAvailableDetails CONSTANT) + Q_PROPERTY(QString releaseNotes READ releaseNotes CONSTANT) public: UpdateDialog(QQuickItem* parent = nullptr); const QString& updateAvailableDetails() const; const QString& releaseNotes() const; - + private: QString _updateAvailableDetails; QString _releaseNotes; protected: - void hide(); Q_INVOKABLE void triggerUpgrade(); - Q_INVOKABLE void closeDialog(); }; diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 41e7e517b7..e602dec48c 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -17,7 +17,7 @@ class Base3DOverlay : public Overlay { Q_OBJECT - + public: Base3DOverlay(); Base3DOverlay(const Base3DOverlay* base3DOverlay); @@ -27,10 +27,10 @@ public: const glm::vec3& getPosition() const { return _transform.getTranslation(); } const glm::quat& getRotation() const { return _transform.getRotation(); } const glm::vec3& getScale() const { return _transform.getScale(); } - + // TODO: consider implementing registration points in this class const glm::vec3& getCenter() const { return getPosition(); } - + float getLineWidth() const { return _lineWidth; } bool getIsSolid() const { return _isSolid; } bool getIsDashedLine() const { return _isDashedLine; } @@ -43,7 +43,7 @@ public: void setRotation(const glm::quat& value) { _transform.setRotation(value); } void setScale(float value) { _transform.setScale(value); } void setScale(const glm::vec3& value) { _transform.setScale(value); } - + void setLineWidth(float lineWidth) { _lineWidth = lineWidth; } void setIsSolid(bool isSolid) { _isSolid = isSolid; } void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } @@ -55,22 +55,22 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal); - virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, + virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) { return findRayIntersection(origin, direction, distance, face, surfaceNormal); } protected: Transform _transform; - + float _lineWidth; bool _isSolid; bool _isDashedLine; bool _ignoreRayIntersection; bool _drawInFront; }; - + #endif // hifi_Base3DOverlay_h diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 8fb3a36919..6ebfd5c71c 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -14,30 +14,11 @@ #include #include - QString const Circle3DOverlay::TYPE = "circle3d"; -Circle3DOverlay::Circle3DOverlay() : - _startAt(0.0f), - _endAt(360.0f), - _outerRadius(1.0f), - _innerRadius(0.0f), - _hasTickMarks(false), - _majorTickMarksAngle(0.0f), - _minorTickMarksAngle(0.0f), - _majorTickMarksLength(0.0f), - _minorTickMarksLength(0.0f), - _quadVerticesID(GeometryCache::UNKNOWN_ID), - _lineVerticesID(GeometryCache::UNKNOWN_ID), - _majorTicksVerticesID(GeometryCache::UNKNOWN_ID), - _minorTicksVerticesID(GeometryCache::UNKNOWN_ID), - _lastStartAt(-1.0f), - _lastEndAt(-1.0f), - _lastOuterRadius(-1.0f), - _lastInnerRadius(-1.0f) -{ - _majorTickMarksColor.red = _majorTickMarksColor.green = _majorTickMarksColor.blue = (unsigned char)0; - _minorTickMarksColor.red = _minorTickMarksColor.green = _minorTickMarksColor.blue = (unsigned char)0; +Circle3DOverlay::Circle3DOverlay() { + memset(&_minorTickMarksColor, 0, sizeof(_minorTickMarksColor)); + memset(&_majorTickMarksColor, 0, sizeof(_majorTickMarksColor)); } Circle3DOverlay::Circle3DOverlay(const Circle3DOverlay* circle3DOverlay) : @@ -56,11 +37,7 @@ Circle3DOverlay::Circle3DOverlay(const Circle3DOverlay* circle3DOverlay) : _quadVerticesID(GeometryCache::UNKNOWN_ID), _lineVerticesID(GeometryCache::UNKNOWN_ID), _majorTicksVerticesID(GeometryCache::UNKNOWN_ID), - _minorTicksVerticesID(GeometryCache::UNKNOWN_ID), - _lastStartAt(-1.0f), - _lastEndAt(-1.0f), - _lastOuterRadius(-1.0f), - _lastInnerRadius(-1.0f) + _minorTicksVerticesID(GeometryCache::UNKNOWN_ID) { } @@ -70,36 +47,25 @@ void Circle3DOverlay::render(RenderArgs* args) { } float alpha = getAlpha(); - if (alpha == 0.0f) { return; // do nothing if our alpha is 0, we're not visible } - // Create the circle in the coordinates origin - float outerRadius = getOuterRadius(); - float innerRadius = getInnerRadius(); // only used in solid case - float startAt = getStartAt(); - float endAt = getEndAt(); - - bool geometryChanged = (startAt != _lastStartAt || endAt != _lastEndAt || - innerRadius != _lastInnerRadius || outerRadius != _lastOuterRadius); - + bool geometryChanged = _dirty; + _dirty = false; const float FULL_CIRCLE = 360.0f; const float SLICES = 180.0f; // The amount of segment to create the circle const float SLICE_ANGLE = FULL_CIRCLE / SLICES; - - xColor colorX = getColor(); const float MAX_COLOR = 255.0f; - glm::vec4 color(colorX.red / MAX_COLOR, colorX.green / MAX_COLOR, colorX.blue / MAX_COLOR, alpha); - - bool colorChanged = colorX.red != _lastColor.red || colorX.green != _lastColor.green || colorX.blue != _lastColor.blue; - _lastColor = colorX; auto geometryCache = DependencyManager::get(); - + Q_ASSERT(args->_batch); auto& batch = *args->_batch; + if (args->_pipeline) { + batch.setPipeline(args->_pipeline->pipeline); + } // FIXME: THe line width of _lineWidth is not supported anymore, we ll need a workaround @@ -110,81 +76,89 @@ void Circle3DOverlay::render(RenderArgs* args) { // for our overlay, is solid means we draw a ring between the inner and outer radius of the circle, otherwise // we just draw a line... if (getIsSolid()) { - if (_quadVerticesID == GeometryCache::UNKNOWN_ID) { + if (!_quadVerticesID) { _quadVerticesID = geometryCache->allocateID(); } - if (geometryChanged || colorChanged) { - + if (geometryChanged) { QVector points; - - float angle = startAt; - float angleInRadians = glm::radians(angle); - glm::vec2 mostRecentInnerPoint(cosf(angleInRadians) * innerRadius, sinf(angleInRadians) * innerRadius); - glm::vec2 mostRecentOuterPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); - - while (angle < endAt) { - angleInRadians = glm::radians(angle); - glm::vec2 thisInnerPoint(cosf(angleInRadians) * innerRadius, sinf(angleInRadians) * innerRadius); - glm::vec2 thisOuterPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); - - points << mostRecentInnerPoint << mostRecentOuterPoint << thisOuterPoint; // first triangle - points << mostRecentInnerPoint << thisInnerPoint << thisOuterPoint; // second triangle - - angle += SLICE_ANGLE; + QVector colors; - mostRecentInnerPoint = thisInnerPoint; - mostRecentOuterPoint = thisOuterPoint; + float pulseLevel = updatePulse(); + vec4 pulseModifier = vec4(1); + if (_alphaPulse != 0.0f) { + pulseModifier.a = (_alphaPulse >= 0.0f) ? pulseLevel : (1.0f - pulseLevel); } - - // get the last slice portion.... - angle = endAt; - angleInRadians = glm::radians(angle); - glm::vec2 lastInnerPoint(cosf(angleInRadians) * innerRadius, sinf(angleInRadians) * innerRadius); - glm::vec2 lastOuterPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); + if (_colorPulse != 0.0f) { + float pulseValue = (_colorPulse >= 0.0f) ? pulseLevel : (1.0f - pulseLevel); + pulseModifier = vec4(vec3(pulseValue), pulseModifier.a); + } + vec4 innerStartColor = vec4(toGlm(_innerStartColor), _innerStartAlpha) * pulseModifier; + vec4 outerStartColor = vec4(toGlm(_outerStartColor), _outerStartAlpha) * pulseModifier; + vec4 innerEndColor = vec4(toGlm(_innerEndColor), _innerEndAlpha) * pulseModifier; + vec4 outerEndColor = vec4(toGlm(_outerEndColor), _outerEndAlpha) * pulseModifier; - points << mostRecentInnerPoint << mostRecentOuterPoint << lastOuterPoint; // first triangle - points << mostRecentInnerPoint << lastInnerPoint << lastOuterPoint; // second triangle - - geometryCache->updateVertices(_quadVerticesID, points, color); + if (_innerRadius <= 0) { + _solidPrimitive = gpu::TRIANGLE_FAN; + points << vec2(); + colors << innerStartColor; + for (float angle = _startAt; angle <= _endAt; angle += SLICE_ANGLE) { + float range = (angle - _startAt) / (_endAt - _startAt); + float angleRadians = glm::radians(angle); + points << glm::vec2(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); + colors << glm::mix(outerStartColor, outerEndColor, range); + } + } else { + _solidPrimitive = gpu::TRIANGLE_STRIP; + for (float angle = _startAt; angle <= _endAt; angle += SLICE_ANGLE) { + float range = (angle - _startAt) / (_endAt - _startAt); + + float angleRadians = glm::radians(angle); + points << glm::vec2(cosf(angleRadians) * _innerRadius, sinf(angleRadians) * _innerRadius); + colors << glm::mix(innerStartColor, innerEndColor, range); + + points << glm::vec2(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); + colors << glm::mix(outerStartColor, outerEndColor, range); + } + } + geometryCache->updateVertices(_quadVerticesID, points, colors); } - geometryCache->renderVertices(batch, gpu::TRIANGLES, _quadVerticesID); + geometryCache->renderVertices(batch, _solidPrimitive, _quadVerticesID); } else { - if (_lineVerticesID == GeometryCache::UNKNOWN_ID) { + if (!_lineVerticesID) { _lineVerticesID = geometryCache->allocateID(); } - if (geometryChanged || colorChanged) { + if (geometryChanged) { QVector points; - float angle = startAt; + float angle = _startAt; float angleInRadians = glm::radians(angle); - glm::vec2 firstPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); + glm::vec2 firstPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << firstPoint; - while (angle < endAt) { + while (angle < _endAt) { angle += SLICE_ANGLE; angleInRadians = glm::radians(angle); - glm::vec2 thisPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); + glm::vec2 thisPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << thisPoint; if (getIsDashedLine()) { angle += SLICE_ANGLE / 2.0f; // short gap angleInRadians = glm::radians(angle); - glm::vec2 dashStartPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); + glm::vec2 dashStartPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << dashStartPoint; } } // get the last slice portion.... - angle = endAt; + angle = _endAt; angleInRadians = glm::radians(angle); - glm::vec2 lastPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); + glm::vec2 lastPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << lastPoint; - - geometryCache->updateVertices(_lineVerticesID, points, color); + geometryCache->updateVertices(_lineVerticesID, points, vec4(toGlm(getColor()), getAlpha())); } if (getIsDashedLine()) { @@ -214,13 +188,13 @@ void Circle3DOverlay::render(RenderArgs* args) { if (getMajorTickMarksAngle() > 0.0f && getMajorTickMarksLength() != 0.0f) { float tickMarkAngle = getMajorTickMarksAngle(); - float angle = startAt - fmodf(startAt, tickMarkAngle) + tickMarkAngle; + float angle = _startAt - fmodf(_startAt, tickMarkAngle) + tickMarkAngle; float angleInRadians = glm::radians(angle); float tickMarkLength = getMajorTickMarksLength(); - float startRadius = (tickMarkLength > 0.0f) ? innerRadius : outerRadius; + float startRadius = (tickMarkLength > 0.0f) ? _innerRadius : _outerRadius; float endRadius = startRadius + tickMarkLength; - while (angle <= endAt) { + while (angle <= _endAt) { angleInRadians = glm::radians(angle); glm::vec2 thisPointA(cosf(angleInRadians) * startRadius, sinf(angleInRadians) * startRadius); @@ -236,13 +210,13 @@ void Circle3DOverlay::render(RenderArgs* args) { if (getMinorTickMarksAngle() > 0.0f && getMinorTickMarksLength() != 0.0f) { float tickMarkAngle = getMinorTickMarksAngle(); - float angle = startAt - fmodf(startAt, tickMarkAngle) + tickMarkAngle; + float angle = _startAt - fmodf(_startAt, tickMarkAngle) + tickMarkAngle; float angleInRadians = glm::radians(angle); float tickMarkLength = getMinorTickMarksLength(); - float startRadius = (tickMarkLength > 0.0f) ? innerRadius : outerRadius; + float startRadius = (tickMarkLength > 0.0f) ? _innerRadius : _outerRadius; float endRadius = startRadius + tickMarkLength; - while (angle <= endAt) { + while (angle <= _endAt) { angleInRadians = glm::radians(angle); glm::vec2 thisPointA(cosf(angleInRadians) * startRadius, sinf(angleInRadians) * startRadius); @@ -269,89 +243,115 @@ void Circle3DOverlay::render(RenderArgs* args) { geometryCache->renderVertices(batch, gpu::LINES, _minorTicksVerticesID); } - - if (geometryChanged) { - _lastStartAt = startAt; - _lastEndAt = endAt; - _lastInnerRadius = innerRadius; - _lastOuterRadius = outerRadius; - } } const render::ShapeKey Circle3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder().withoutCullFace(); + auto builder = render::ShapeKey::Builder().withoutCullFace().withUnlit(); if (getAlpha() != 1.0f) { builder.withTranslucent(); } + if (!getIsSolid()) { + builder.withUnlit().withDepthBias(); + } return builder.build(); } +template T fromVariant(const QVariant& v, bool& valid) { + valid = v.isValid(); + return qvariant_cast(v); +} + +template<> xColor fromVariant(const QVariant& v, bool& valid) { + return xColorFromVariant(v, valid); +} + +template +bool updateIfValid(const QVariantMap& properties, const char* key, T& output) { + bool valid; + T result = fromVariant(properties[key], valid); + if (!valid) { + return false; + } + + // Don't signal updates if the value was already set + if (result == output) { + return false; + } + + output = result; + return true; +} + +// Multicast, many outputs +template +bool updateIfValid(const QVariantMap& properties, const char* key, std::initializer_list> outputs) { + bool valid; + T value = fromVariant(properties[key], valid); + if (!valid) { + return false; + } + bool updated = false; + for (T& output : outputs) { + if (output != value) { + output = value; + updated = true; + } + } + return updated; +} + +// Multicast, multiple possible inputs, in order of preference +template +bool updateIfValid(const QVariantMap& properties, const std::initializer_list keys, T& output) { + for (const char* key : keys) { + if (updateIfValid(properties, key, output)) { + return true; + } + } + return false; +} + + void Circle3DOverlay::setProperties(const QVariantMap& properties) { Planar3DOverlay::setProperties(properties); + _dirty |= updateIfValid(properties, "alpha", { _innerStartAlpha, _innerEndAlpha, _outerStartAlpha, _outerEndAlpha }); + _dirty |= updateIfValid(properties, "Alpha", { _innerStartAlpha, _innerEndAlpha, _outerStartAlpha, _outerEndAlpha }); + _dirty |= updateIfValid(properties, "startAlpha", { _innerStartAlpha, _outerStartAlpha }); + _dirty |= updateIfValid(properties, "endAlpha", { _innerEndAlpha, _outerEndAlpha }); + _dirty |= updateIfValid(properties, "innerAlpha", { _innerStartAlpha, _innerEndAlpha }); + _dirty |= updateIfValid(properties, "outerAlpha", { _outerStartAlpha, _outerEndAlpha }); + _dirty |= updateIfValid(properties, "innerStartAlpha", _innerStartAlpha); + _dirty |= updateIfValid(properties, "innerEndAlpha", _innerEndAlpha); + _dirty |= updateIfValid(properties, "outerStartAlpha", _outerStartAlpha); + _dirty |= updateIfValid(properties, "outerEndAlpha", _outerEndAlpha); - QVariant startAt = properties["startAt"]; - if (startAt.isValid()) { - setStartAt(startAt.toFloat()); - } + _dirty |= updateIfValid(properties, "color", { _innerStartColor, _innerEndColor, _outerStartColor, _outerEndColor }); + _dirty |= updateIfValid(properties, "startColor", { _innerStartColor, _outerStartColor } ); + _dirty |= updateIfValid(properties, "endColor", { _innerEndColor, _outerEndColor } ); + _dirty |= updateIfValid(properties, "innerColor", { _innerStartColor, _innerEndColor } ); + _dirty |= updateIfValid(properties, "outerColor", { _outerStartColor, _outerEndColor } ); + _dirty |= updateIfValid(properties, "innerStartColor", _innerStartColor); + _dirty |= updateIfValid(properties, "innerEndColor", _innerEndColor); + _dirty |= updateIfValid(properties, "outerStartColor", _outerStartColor); + _dirty |= updateIfValid(properties, "outerEndColor", _outerEndColor); - QVariant endAt = properties["endAt"]; - if (endAt.isValid()) { - setEndAt(endAt.toFloat()); - } + _dirty |= updateIfValid(properties, "startAt", _startAt); + _dirty |= updateIfValid(properties, "endAt", _endAt); - QVariant outerRadius = properties["radius"]; - if (!outerRadius.isValid()) { - outerRadius = properties["outerRadius"]; - } - if (outerRadius.isValid()) { - setOuterRadius(outerRadius.toFloat()); - } + _dirty |= updateIfValid(properties, { "radius", "outerRadius" }, _outerRadius); + _dirty |= updateIfValid(properties, "innerRadius", _innerRadius); + _dirty |= updateIfValid(properties, "hasTickMarks", _hasTickMarks); + _dirty |= updateIfValid(properties, "majorTickMarksAngle", _majorTickMarksAngle); + _dirty |= updateIfValid(properties, "minorTickMarksAngle", _minorTickMarksAngle); + _dirty |= updateIfValid(properties, "majorTickMarksLength", _majorTickMarksLength); + _dirty |= updateIfValid(properties, "minorTickMarksLength", _minorTickMarksLength); + _dirty |= updateIfValid(properties, "majorTickMarksColor", _majorTickMarksColor); + _dirty |= updateIfValid(properties, "minorTickMarksColor", _minorTickMarksColor); - QVariant innerRadius = properties["innerRadius"]; - if (innerRadius.isValid()) { - setInnerRadius(innerRadius.toFloat()); - } - - QVariant hasTickMarks = properties["hasTickMarks"]; - if (hasTickMarks.isValid()) { - setHasTickMarks(hasTickMarks.toBool()); - } - - QVariant majorTickMarksAngle = properties["majorTickMarksAngle"]; - if (majorTickMarksAngle.isValid()) { - setMajorTickMarksAngle(majorTickMarksAngle.toFloat()); - } - - QVariant minorTickMarksAngle = properties["minorTickMarksAngle"]; - if (minorTickMarksAngle.isValid()) { - setMinorTickMarksAngle(minorTickMarksAngle.toFloat()); - } - - QVariant majorTickMarksLength = properties["majorTickMarksLength"]; - if (majorTickMarksLength.isValid()) { - setMajorTickMarksLength(majorTickMarksLength.toFloat()); - } - - QVariant minorTickMarksLength = properties["minorTickMarksLength"]; - if (minorTickMarksLength.isValid()) { - setMinorTickMarksLength(minorTickMarksLength.toFloat()); - } - - bool valid; - auto majorTickMarksColor = properties["majorTickMarksColor"]; - if (majorTickMarksColor.isValid()) { - auto color = xColorFromVariant(majorTickMarksColor, valid); - if (valid) { - _majorTickMarksColor = color; - } - } - - auto minorTickMarksColor = properties["minorTickMarksColor"]; - if (minorTickMarksColor.isValid()) { - auto color = xColorFromVariant(majorTickMarksColor, valid); - if (valid) { - _minorTickMarksColor = color; - } + if (_innerStartAlpha < 1.0f || _innerEndAlpha < 1.0f || _outerStartAlpha < 1.0f || _outerEndAlpha < 1.0f) { + // Force the alpha to 0.5, since we'll ignore it in the presence of these other values, but we need + // it to be non-1 in order to get the right pipeline and non-0 in order to render at all. + _alpha = 0.5f; } } diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index c0e84ef1c6..82c7c47dc7 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -59,28 +59,34 @@ public: virtual Circle3DOverlay* createClone() const override; protected: - float _startAt; - float _endAt; - float _outerRadius; - float _innerRadius; - bool _hasTickMarks; - float _majorTickMarksAngle; - float _minorTickMarksAngle; - float _majorTickMarksLength; - float _minorTickMarksLength; + float _startAt { 0 }; + float _endAt { 360 }; + float _outerRadius { 1 }; + float _innerRadius { 0 }; + + xColor _innerStartColor; + xColor _innerEndColor; + xColor _outerStartColor; + xColor _outerEndColor; + float _innerStartAlpha; + float _innerEndAlpha; + float _outerStartAlpha; + float _outerEndAlpha; + + bool _hasTickMarks { false }; + float _majorTickMarksAngle { 0 }; + float _minorTickMarksAngle { 0 }; + float _majorTickMarksLength { 0 }; + float _minorTickMarksLength { 0 }; xColor _majorTickMarksColor; xColor _minorTickMarksColor; - - int _quadVerticesID; - int _lineVerticesID; - int _majorTicksVerticesID; - int _minorTicksVerticesID; + gpu::Primitive _solidPrimitive { gpu::TRIANGLE_FAN }; + int _quadVerticesID { 0 }; + int _lineVerticesID { 0 }; + int _majorTicksVerticesID { 0 }; + int _minorTicksVerticesID { 0 }; - xColor _lastColor; - float _lastStartAt; - float _lastEndAt; - float _lastOuterRadius; - float _lastInnerRadius; + bool _dirty { true }; }; diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index 38650f9fda..f72fb8d920 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -44,11 +44,10 @@ void Cube3DOverlay::render(RenderArgs* args) { Transform transform; transform.setTranslation(position); transform.setRotation(rotation); - auto geometryCache = DependencyManager::get(); auto pipeline = args->_pipeline; if (!pipeline) { - pipeline = geometryCache->getShapePipeline(); + pipeline = _isSolid ? geometryCache->getShapePipeline() : geometryCache->getWireShapePipeline(); } if (_isSolid) { @@ -56,7 +55,7 @@ void Cube3DOverlay::render(RenderArgs* args) { batch->setModelTransform(transform); geometryCache->renderSolidCubeInstance(*batch, cubeColor, pipeline); } else { - + geometryCache->bindSimpleProgram(*batch, false, false, true, true); if (getIsDashedLine()) { transform.setScale(1.0f); batch->setModelTransform(transform); @@ -101,6 +100,9 @@ const render::ShapeKey Cube3DOverlay::getShapeKey() { if (getAlpha() != 1.0f) { builder.withTranslucent(); } + if (!getIsSolid()) { + builder.withUnlit().withDepthBias(); + } return builder.build(); } diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp index edc27e35f2..e9bbcddf59 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -75,7 +75,6 @@ void Grid3DOverlay::render(RenderArgs* args) { transform.setScale(glm::vec3(getDimensions(), 1.0f)); transform.setTranslation(position); batch->setModelTransform(transform); - const float MINOR_GRID_EDGE = 0.0025f; const float MAJOR_GRID_EDGE = 0.005f; DependencyManager::get()->renderGrid(*batch, minCorner, maxCorner, @@ -86,7 +85,7 @@ void Grid3DOverlay::render(RenderArgs* args) { } const render::ShapeKey Grid3DOverlay::getShapeKey() { - return render::ShapeKey::Builder().withOwnPipeline(); + return render::ShapeKey::Builder().withOwnPipeline().withUnlit().withDepthBias(); } void Grid3DOverlay::setProperties(const QVariantMap& properties) { diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index fd0d2bcedf..d59e552779 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -92,7 +92,7 @@ void Image3DOverlay::render(RenderArgs* args) { batch->setModelTransform(transform); batch->setResourceTexture(0, _texture->getGPUTexture()); - + DependencyManager::get()->renderQuad( *batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, glm::vec4(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha) diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 3971e91211..c9a8b19f6a 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -54,18 +54,23 @@ void Line3DOverlay::render(RenderArgs* args) { if (batch) { batch->setModelTransform(_transform); + auto geometryCache = DependencyManager::get(); if (getIsDashedLine()) { // TODO: add support for color to renderDashedLine() - DependencyManager::get()->renderDashedLine(*batch, _start, _end, colorv4, _geometryCacheID); + geometryCache->bindSimpleProgram(*batch, false, false, true, true); + geometryCache->renderDashedLine(*batch, _start, _end, colorv4, _geometryCacheID); + } else if (_glow > 0.0f) { + geometryCache->renderGlowLine(*batch, _start, _end, colorv4, _glow, _glowWidth, _geometryCacheID); } else { - DependencyManager::get()->renderLine(*batch, _start, _end, colorv4, _geometryCacheID); + geometryCache->bindSimpleProgram(*batch, false, false, true, true); + geometryCache->renderLine(*batch, _start, _end, colorv4, _geometryCacheID); } } } const render::ShapeKey Line3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder().withoutCullFace(); - if (getAlpha() != 1.0f) { + auto builder = render::ShapeKey::Builder().withOwnPipeline(); + if (getAlpha() != 1.0f || _glow > 0.0f) { builder.withTranslucent(); } return builder.build(); @@ -91,6 +96,19 @@ void Line3DOverlay::setProperties(const QVariantMap& properties) { if (end.isValid()) { setEnd(vec3FromVariant(end)); } + + auto glow = properties["glow"]; + if (glow.isValid()) { + setGlow(glow.toFloat()); + if (_glow > 0.0f) { + _alpha = 0.5f; + } + } + + auto glowWidth = properties["glow"]; + if (glowWidth.isValid()) { + setGlow(glowWidth.toFloat()); + } } QVariant Line3DOverlay::getProperty(const QString& property) { diff --git a/interface/src/ui/overlays/Line3DOverlay.h b/interface/src/ui/overlays/Line3DOverlay.h index db50d11276..d066677c70 100644 --- a/interface/src/ui/overlays/Line3DOverlay.h +++ b/interface/src/ui/overlays/Line3DOverlay.h @@ -30,10 +30,14 @@ public: // getters const glm::vec3& getStart() const { return _start; } const glm::vec3& getEnd() const { return _end; } + const float& getGlow() const { return _glow; } + const float& getGlowWidth() const { return _glowWidth; } // setters void setStart(const glm::vec3& start) { _start = start; } void setEnd(const glm::vec3& end) { _end = end; } + void setGlow(const float& glow) { _glow = glow; } + void setGlowWidth(const float& glowWidth) { _glowWidth = glowWidth; } void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; @@ -43,6 +47,8 @@ public: protected: glm::vec3 _start; glm::vec3 _end; + float _glow { 0.0 }; + float _glowWidth { 0.0 }; int _geometryCacheID; }; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index adf08934f0..9c203c0129 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -19,8 +19,7 @@ QString const ModelOverlay::TYPE = "model"; ModelOverlay::ModelOverlay() : _model(std::make_shared(std::make_shared())), - _modelTextures(QVariantMap()), - _updateModel(false) + _modelTextures(QVariantMap()) { _model->init(); _isLoaded = false; @@ -43,9 +42,12 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : void ModelOverlay::update(float deltatime) { if (_updateModel) { _updateModel = false; - _model->setSnapModelToCenter(true); - _model->setScale(getDimensions()); + if (_scaleToFit) { + _model->setScaleToFit(true, getScale() * getDimensions()); + } else { + _model->setScale(getScale()); + } _model->setRotation(getRotation()); _model->setTranslation(getPosition()); _model->setURL(_url); @@ -85,41 +87,47 @@ void ModelOverlay::render(RenderArgs* args) { } void ModelOverlay::setProperties(const QVariantMap& properties) { - auto position = getPosition(); - auto rotation = getRotation(); - auto scale = getDimensions(); - - Volume3DOverlay::setProperties(properties); - - if (position != getPosition() || rotation != getRotation()) { + auto origPosition = getPosition(); + auto origRotation = getRotation(); + auto origDimensions = getDimensions(); + auto origScale = getScale(); + + Base3DOverlay::setProperties(properties); + + auto scale = properties["scale"]; + if (scale.isValid()) { + setScale(vec3FromVariant(scale)); + } + + auto dimensions = properties["dimensions"]; + if (dimensions.isValid()) { + _scaleToFit = true; + setDimensions(vec3FromVariant(dimensions)); + } else if (scale.isValid()) { + // if "scale" property is set but "dimentions" is not. + // do NOT scale to fit. + _scaleToFit = false; + } + + if (origPosition != getPosition() || origRotation != getRotation() || origDimensions != getDimensions() || origScale != getScale()) { _updateModel = true; } - if (scale != getDimensions()) { - auto newScale = getDimensions(); - if (newScale.x <= 0 || newScale.y <= 0 || newScale.z <= 0) { - setDimensions(scale); - } else { - _model->setScaleToFit(true, getDimensions()); - _updateModel = true; - } - } - auto urlValue = properties["url"]; if (urlValue.isValid() && urlValue.canConvert()) { _url = urlValue.toString(); _updateModel = true; _isLoaded = false; } - + auto texturesValue = properties["textures"]; if (texturesValue.isValid() && texturesValue.canConvert(QVariant::Map)) { QVariantMap textureMap = texturesValue.toMap(); foreach(const QString& key, textureMap.keys()) { - + QUrl newTextureURL = textureMap[key].toUrl(); qDebug() << "Updating texture named" << key << "to texture at URL" << newTextureURL; - + QMetaObject::invokeMethod(_model.get(), "setTextureWithNameToURL", Qt::AutoConnection, Q_ARG(const QString&, key), Q_ARG(const QUrl&, newTextureURL)); @@ -133,8 +141,11 @@ QVariant ModelOverlay::getProperty(const QString& property) { if (property == "url") { return _url.toString(); } - if (property == "dimensions" || property == "scale" || property == "size") { - return vec3toVariant(_model->getScaleToFitDimensions()); + if (property == "dimensions" || property == "size") { + return vec3toVariant(getDimensions()); + } + if (property == "scale") { + return vec3toVariant(getScale()); } if (property == "textures") { if (_modelTextures.size() > 0) { @@ -153,14 +164,14 @@ QVariant ModelOverlay::getProperty(const QString& property) { bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) { - + QString subMeshNameTemp; return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp); } bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) { - + return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo); } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index dc4b4a853b..091cab44c9 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -43,9 +43,10 @@ private: ModelPointer _model; QVariantMap _modelTextures; - + QUrl _url; - bool _updateModel; + bool _updateModel = { false }; + bool _scaleToFit = { false }; }; #endif // hifi_ModelOverlay_h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 9ff7f6268f..e99ca3a9e0 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -22,6 +22,7 @@ #include "Image3DOverlay.h" #include "Circle3DOverlay.h" #include "Cube3DOverlay.h" +#include "Shape3DOverlay.h" #include "ImageOverlay.h" #include "Line3DOverlay.h" #include "LocalModelsOverlay.h" @@ -157,6 +158,8 @@ unsigned int Overlays::addOverlay(const QString& type, const QVariant& propertie thisOverlay = std::make_shared(); } else if (type == Text3DOverlay::TYPE) { thisOverlay = std::make_shared(); + } else if (type == Shape3DOverlay::TYPE) { + thisOverlay = std::make_shared(); } else if (type == Cube3DOverlay::TYPE) { thisOverlay = std::make_shared(); } else if (type == Sphere3DOverlay::TYPE) { diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index 5a541fd58a..75d7ec565c 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -53,13 +53,15 @@ void Rectangle3DOverlay::render(RenderArgs* args) { transform.setRotation(rotation); batch->setModelTransform(transform); + auto geometryCache = DependencyManager::get(); if (getIsSolid()) { glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, 0.0f); glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, 0.0f); - DependencyManager::get()->renderQuad(*batch, topLeft, bottomRight, rectangleColor); + geometryCache->bindSimpleProgram(*batch); + geometryCache->renderQuad(*batch, topLeft, bottomRight, rectangleColor); } else { - auto geometryCache = DependencyManager::get(); + geometryCache->bindSimpleProgram(*batch, false, false, true, true); if (getIsDashedLine()) { glm::vec3 point1(-halfDimensions.x, -halfDimensions.y, 0.0f); glm::vec3 point2(halfDimensions.x, -halfDimensions.y, 0.0f); @@ -89,7 +91,7 @@ void Rectangle3DOverlay::render(RenderArgs* args) { } const render::ShapeKey Rectangle3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder(); + auto builder = render::ShapeKey::Builder().withOwnPipeline(); if (getAlpha() != 1.0f) { builder.withTranslucent(); } diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp new file mode 100644 index 0000000000..cd07385aab --- /dev/null +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -0,0 +1,130 @@ +// +// Shape3DOverlay.cpp +// interface/src/ui/overlays +// +// 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 this before QGLWidget, which includes an earlier version of OpenGL +#include "Shape3DOverlay.h" + +#include +#include +#include +#include + +QString const Shape3DOverlay::TYPE = "shape"; + +Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay) : + Volume3DOverlay(Shape3DOverlay) +{ +} + +void Shape3DOverlay::render(RenderArgs* args) { + if (!_visible) { + return; // do nothing if we're not visible + } + + float alpha = getAlpha(); + xColor color = getColor(); + const float MAX_COLOR = 255.0f; + glm::vec4 cubeColor(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); + + // TODO: handle registration point?? + glm::vec3 position = getPosition(); + glm::vec3 dimensions = getDimensions(); + glm::quat rotation = getRotation(); + + auto batch = args->_batch; + + if (batch) { + Transform transform; + transform.setTranslation(position); + transform.setRotation(rotation); + auto geometryCache = DependencyManager::get(); + auto pipeline = args->_pipeline; + if (!pipeline) { + pipeline = _isSolid ? geometryCache->getShapePipeline() : geometryCache->getWireShapePipeline(); + } + + transform.setScale(dimensions); + batch->setModelTransform(transform); + if (_isSolid) { + geometryCache->renderSolidShapeInstance(*batch, _shape, cubeColor, pipeline); + } else { + geometryCache->renderWireShapeInstance(*batch, _shape, cubeColor, pipeline); + } + } +} + +const render::ShapeKey Shape3DOverlay::getShapeKey() { + auto builder = render::ShapeKey::Builder(); + if (getAlpha() != 1.0f) { + builder.withTranslucent(); + } + if (!getIsSolid()) { + builder.withUnlit().withDepthBias(); + } + return builder.build(); +} + +Shape3DOverlay* Shape3DOverlay::createClone() const { + return new Shape3DOverlay(this); +} + + +static const std::array shapeStrings { { + "Line", + "Triangle", + "Quad", + "Hexagon", + "Octagon", + "Circle", + "Cube", + "Sphere", + "Tetrahedron", + "Octahedron", + "Dodecahedron", + "Icosahedron", + "Torus", + "Cone", + "Cylinder" +} }; + + +void Shape3DOverlay::setProperties(const QVariantMap& properties) { + Volume3DOverlay::setProperties(properties); + + auto shape = properties["shape"]; + if (shape.isValid()) { + const QString shapeStr = shape.toString(); + for (size_t i = 0; i < shapeStrings.size(); ++i) { + if (shapeStr == shapeStrings[i]) { + this->_shape = static_cast(i); + break; + } + } + } + + auto borderSize = properties["borderSize"]; + + if (borderSize.isValid()) { + float value = borderSize.toFloat(); + setBorderSize(value); + } +} + +QVariant Shape3DOverlay::getProperty(const QString& property) { + if (property == "borderSize") { + return _borderSize; + } + + if (property == "shape") { + return shapeStrings[_shape]; + } + + return Volume3DOverlay::getProperty(property); +} diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h new file mode 100644 index 0000000000..2361001721 --- /dev/null +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -0,0 +1,46 @@ +// +// Shape3DOverlay.h +// interface/src/ui/overlays +// +// 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_Shape3DOverlay_h +#define hifi_Shape3DOverlay_h + +#include "Volume3DOverlay.h" + +#include + +class Shape3DOverlay : public Volume3DOverlay { + Q_OBJECT + +public: + static QString const TYPE; + virtual QString getType() const override { return TYPE; } + + Shape3DOverlay() {} + Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay); + + virtual void render(RenderArgs* args) override; + virtual const render::ShapeKey getShapeKey() override; + + virtual Shape3DOverlay* createClone() const override; + + float getBorderSize() const { return _borderSize; } + + void setBorderSize(float value) { _borderSize = value; } + + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; + +private: + float _borderSize; + GeometryCache::Shape _shape { GeometryCache::Hexagon }; +}; + + +#endif // hifi_Shape3DOverlay_h diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index d50f2f7285..bbdd886d11 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -46,7 +46,7 @@ void Sphere3DOverlay::render(RenderArgs* args) { auto geometryCache = DependencyManager::get(); auto pipeline = args->_pipeline; if (!pipeline) { - pipeline = geometryCache->getShapePipeline(); + pipeline = _isSolid ? geometryCache->getShapePipeline() : geometryCache->getWireShapePipeline(); } if (_isSolid) { @@ -62,6 +62,9 @@ const render::ShapeKey Sphere3DOverlay::getShapeKey() { if (getAlpha() != 1.0f) { builder.withTranslucent(); } + if (!getIsSolid()) { + builder.withUnlit().withDepthBias(); + } return builder.build(); } diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index c8078d35c6..563198c976 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -22,7 +22,7 @@ AABox Volume3DOverlay::getBounds() const { auto extents = Extents{_localBoundingBox}; extents.rotate(getRotation()); extents.shiftBy(getPosition()); - + return AABox(extents); } @@ -31,7 +31,7 @@ void Volume3DOverlay::setProperties(const QVariantMap& properties) { auto dimensions = properties["dimensions"]; - // if "dimensions" property was not there, check to see if they included aliases: scale + // if "dimensions" property was not there, check to see if they included aliases: scale, size if (!dimensions.isValid()) { dimensions = properties["scale"]; if (!dimensions.isValid()) { @@ -57,7 +57,7 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve // extents is the entity relative, scaled, centered extents of the entity glm::mat4 worldToEntityMatrix; _transform.getInverseMatrix(worldToEntityMatrix); - + glm::vec3 overlayFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); glm::vec3 overlayFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f)); diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index 4d087615d2..04b694b2f8 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -15,13 +15,13 @@ class Volume3DOverlay : public Base3DOverlay { Q_OBJECT - + public: Volume3DOverlay() {} Volume3DOverlay(const Volume3DOverlay* volume3DOverlay); - + virtual AABox getBounds() const override; - + const glm::vec3& getDimensions() const { return _localBoundingBox.getDimensions(); } void setDimensions(float value) { _localBoundingBox.setBox(glm::vec3(-value / 2.0f), value); } void setDimensions(const glm::vec3& value) { _localBoundingBox.setBox(-value / 2.0f, value); } @@ -29,13 +29,13 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; - + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + BoxFace& face, glm::vec3& surfaceNormal) override; + protected: // Centered local bounding box AABox _localBoundingBox{ vec3(0.0f), 1.0f }; }; - + #endif // hifi_Volume3DOverlay_h diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index f5baecd96a..c9c24d3ab6 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -100,7 +100,9 @@ void Web3DOverlay::render(RenderArgs* args) { } batch.setModelTransform(transform); - DependencyManager::get()->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color); + auto geometryCache = DependencyManager::get(); + geometryCache->bindSimpleProgram(batch, true, false, true, false); + geometryCache->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color); batch.setResourceTexture(0, args->_whiteTexture); // restore default white color after me } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 2d37be9b87..351c09beee 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -107,6 +107,18 @@ void AnimSkeleton::convertAbsolutePosesToRelative(AnimPoseVec& poses) const { } } +void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector& rotations) const { + // poses start off absolute and leave in relative frame + int lastIndex = std::min((int)rotations.size(), (int)_joints.size()); + for (int i = lastIndex - 1; i >= 0; --i) { + int parentIndex = _joints[i].parentIndex; + if (parentIndex != -1) { + rotations[i] = glm::inverse(rotations[parentIndex]) * rotations[i]; + } + } +} + + void AnimSkeleton::mirrorRelativePoses(AnimPoseVec& poses) const { convertRelativePosesToAbsolute(poses); mirrorAbsolutePoses(poses); diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index e2cd20d63e..68cce11326 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -55,6 +55,8 @@ public: void convertRelativePosesToAbsolute(AnimPoseVec& poses) const; void convertAbsolutePosesToRelative(AnimPoseVec& poses) const; + void convertAbsoluteRotationsToRelative(std::vector& rotations) const; + void mirrorRelativePoses(AnimPoseVec& poses) const; void mirrorAbsolutePoses(AnimPoseVec& poses) const; diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 482c4211cb..7601fbc782 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -15,7 +15,7 @@ #include "AnimationCache.h" #include "AnimationLogging.h" -static int animationPointerMetaTypeId = qRegisterMetaType(); +int animationPointerMetaTypeId = qRegisterMetaType(); AnimationCache::AnimationCache(QObject* parent) : ResourceCache(parent) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 67dfbec24a..8aee2245c0 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -163,8 +163,8 @@ void Rig::destroyAnimGraph() { } void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { - _geometryOffset = AnimPose(geometry.offset); + _invGeometryOffset = _geometryOffset.inverse(); setModelOffset(modelOffset); _animSkeleton = std::make_shared(geometry); @@ -193,6 +193,7 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff void Rig::reset(const FBXGeometry& geometry) { _geometryOffset = AnimPose(geometry.offset); + _invGeometryOffset = _geometryOffset.inverse(); _animSkeleton = std::make_shared(geometry); _internalPoseSet._relativePoses.clear(); @@ -272,24 +273,6 @@ void Rig::setModelOffset(const glm::mat4& modelOffsetMat) { } } -bool Rig::getJointStateRotation(int index, glm::quat& rotation) const { - if (isIndexValid(index)) { - rotation = _internalPoseSet._relativePoses[index].rot; - return !isEqual(rotation, _animSkeleton->getRelativeDefaultPose(index).rot); - } else { - return false; - } -} - -bool Rig::getJointStateTranslation(int index, glm::vec3& translation) const { - if (isIndexValid(index)) { - translation = _internalPoseSet._relativePoses[index].trans; - return !isEqual(translation, _animSkeleton->getRelativeDefaultPose(index).trans); - } else { - return false; - } -} - void Rig::clearJointState(int index) { if (isIndexValid(index)) { _internalPoseSet._overrideFlags[index] = false; @@ -947,11 +930,6 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) { } void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { - if (params.enableLean) { - updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist); - } else { - _animVars.unset("lean"); - } updateNeckJoint(params.neckJointIndex, params); _animVars.set("isTalking", params.isTalking); @@ -969,15 +947,6 @@ static const glm::vec3 X_AXIS(1.0f, 0.0f, 0.0f); static const glm::vec3 Y_AXIS(0.0f, 1.0f, 0.0f); static const glm::vec3 Z_AXIS(0.0f, 0.0f, 1.0f); -void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist) { - if (isIndexValid(index)) { - glm::quat absRot = (glm::angleAxis(-RADIANS_PER_DEGREE * leanSideways, Z_AXIS) * - glm::angleAxis(-RADIANS_PER_DEGREE * leanForward, X_AXIS) * - glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, Y_AXIS)); - _animVars.set("lean", absRot); - } -} - void Rig::computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut, glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const { @@ -1229,24 +1198,89 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const { } void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { + + const AnimPose geometryToRigPose(_geometryToRigTransform); + jointDataVec.resize((int)getJointStateCount()); for (auto i = 0; i < jointDataVec.size(); i++) { JointData& data = jointDataVec[i]; - data.rotationSet |= getJointStateRotation(i, data.rotation); - // geometry offset is used here so that translations are in meters. - // this is what the avatar mixer expects - data.translationSet |= getJointStateTranslation(i, data.translation); - data.translation = _geometryOffset * data.translation; + if (isIndexValid(i)) { + // rotations are in absolute rig frame. + glm::quat defaultAbsRot = geometryToRigPose.rot * _animSkeleton->getAbsoluteDefaultPose(i).rot; + data.rotation = _internalPoseSet._absolutePoses[i].rot; + data.rotationSet = !isEqual(data.rotation, defaultAbsRot); + + // translations are in relative frame but scaled so that they are in meters, + // instead of geometry units. + glm::vec3 defaultRelTrans = _geometryOffset.scale * _animSkeleton->getRelativeDefaultPose(i).trans; + data.translation = _geometryOffset.scale * _internalPoseSet._relativePoses[i].trans; + data.translationSet = !isEqual(data.translation, defaultRelTrans); + } else { + data.translationSet = false; + data.rotationSet = false; + } } } void Rig::copyJointsFromJointData(const QVector& jointDataVec) { - AnimPose invGeometryOffset = _geometryOffset.inverse(); - for (int i = 0; i < jointDataVec.size(); i++) { - const JointData& data = jointDataVec.at(i); - setJointRotation(i, data.rotationSet, data.rotation, 1.0f); - // geometry offset is used here to undo the fact that avatar mixer translations are in meters. - setJointTranslation(i, data.translationSet, invGeometryOffset * data.translation, 1.0f); + if (_animSkeleton && jointDataVec.size() == (int)_internalPoseSet._overrideFlags.size()) { + + // transform all the default poses into rig space. + const AnimPose geometryToRigPose(_geometryToRigTransform); + std::vector overrideFlags(_internalPoseSet._overridePoses.size(), false); + + // start with the default rotations in absolute rig frame + std::vector rotations; + rotations.reserve(_animSkeleton->getAbsoluteDefaultPoses().size()); + for (auto& pose : _animSkeleton->getAbsoluteDefaultPoses()) { + rotations.push_back(geometryToRigPose.rot * pose.rot); + } + + // start translations in relative frame but scaled to meters. + std::vector translations; + translations.reserve(_animSkeleton->getRelativeDefaultPoses().size()); + for (auto& pose : _animSkeleton->getRelativeDefaultPoses()) { + translations.push_back(_geometryOffset.scale * pose.trans); + } + + ASSERT(overrideFlags.size() == rotations.size()); + + // copy over rotations from the jointDataVec, which is also in absolute rig frame + for (int i = 0; i < jointDataVec.size(); i++) { + if (isIndexValid(i)) { + const JointData& data = jointDataVec.at(i); + if (data.rotationSet) { + overrideFlags[i] = true; + rotations[i] = data.rotation; + } + if (data.translationSet) { + overrideFlags[i] = true; + translations[i] = data.translation; + } + } + } + + ASSERT(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); + + // convert resulting rotations into geometry space. + const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform)); + for (auto& rot : rotations) { + rot = rigToGeometryRot * rot; + } + + // convert all rotations from absolute to parent relative. + _animSkeleton->convertAbsoluteRotationsToRelative(rotations); + + // copy the geometry space parent relative poses into _overridePoses + for (int i = 0; i < jointDataVec.size(); i++) { + if (overrideFlags[i]) { + _internalPoseSet._overrideFlags[i] = true; + _internalPoseSet._overridePoses[i].scale = Vectors::ONE; + _internalPoseSet._overridePoses[i].rot = rotations[i]; + // scale translations from meters back into geometry units. + _internalPoseSet._overridePoses[i].trans = _invGeometryOffset.scale * translations[i]; + } + } } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 363006d48c..e2193e8479 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -42,15 +42,10 @@ public: }; struct HeadParameters { - float leanSideways = 0.0f; // degrees - float leanForward = 0.0f; // degrees - float torsoTwist = 0.0f; // degrees - bool enableLean = false; glm::quat worldHeadOrientation = glm::quat(); // world space (-z forward) glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward) glm::vec3 rigHeadPosition = glm::vec3(); // rig space bool isInHMD = false; - int leanJointIndex = -1; int neckJointIndex = -1; bool isTalking = false; }; @@ -104,12 +99,6 @@ public: void setModelOffset(const glm::mat4& modelOffsetMat); - // geometry space - bool getJointStateRotation(int index, glm::quat& rotation) const; - - // geometry space - bool getJointStateTranslation(int index, glm::vec3& translation) const; - void clearJointState(int index); void clearJointStates(); void clearJointAnimationPriority(int index); @@ -119,8 +108,6 @@ public: // geometry space void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); - - // geometry space void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); // legacy @@ -230,7 +217,6 @@ protected: void applyOverridePoses(); void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut); - void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist); void updateNeckJoint(int index, const HeadParameters& params); void computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut, glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const; @@ -239,6 +225,7 @@ protected: AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) + AnimPose _invGeometryOffset; struct PoseSet { AnimPoseVec _relativePoses; // geometry space relative to parent. diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt index 2c0fc0a9cd..9c298ce664 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME audio-client) setup_hifi_library(Network Multimedia) -link_hifi_libraries(audio) +link_hifi_libraries(audio plugins) # append audio includes to our list of includes to bubble target_include_directories(${TARGET_NAME} PUBLIC "${HIFI_LIBRARY_DIR}/audio/src") diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 0c7a79e2a3..ac42de903d 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -14,6 +14,8 @@ #include #include +#include +#include #ifdef __APPLE__ #include @@ -34,6 +36,8 @@ #include #include +#include +#include #include #include #include @@ -41,8 +45,6 @@ #include #include -#include "AudioInjector.h" -#include "AudioConstants.h" #include "PositionalAudioStream.h" #include "AudioClientLogging.h" @@ -102,7 +104,7 @@ AudioClient::AudioClient() : _reverbOptions(&_scriptReverbOptions), _inputToNetworkResampler(NULL), _networkToOutputResampler(NULL), - _loopbackResampler(NULL), + _audioLimiter(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO), _outgoingAvatarAudioSequenceNumber(0), _audioOutputIODevice(_receivedAudioStream, this), _stats(&_receivedAudioStream), @@ -134,10 +136,15 @@ AudioClient::AudioClient() : packetReceiver.registerListener(PacketType::MixedAudio, this, "handleAudioDataPacket"); packetReceiver.registerListener(PacketType::NoisyMute, this, "handleNoisyMutePacket"); packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); + packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); } AudioClient::~AudioClient() { stop(); + if (_codec && _encoder) { + _codec->releaseEncoder(_encoder); + _encoder = nullptr; + } } void AudioClient::reset() { @@ -315,54 +322,35 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, // FIXME: directly using 24khz has a bug somewhere that causes channels to be swapped. // Continue using our internal resampler, for now. - if (true || !audioDevice.isFormatSupported(desiredAudioFormat)) { - qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat; - qCDebug(audioclient, "The desired audio format is not supported by this device"); + qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat; - if (desiredAudioFormat.channelCount() == 1) { - adjustedAudioFormat = desiredAudioFormat; - adjustedAudioFormat.setChannelCount(2); - - if (false && audioDevice.isFormatSupported(adjustedAudioFormat)) { - return true; - } else { - adjustedAudioFormat.setChannelCount(1); - } - } - - const int FORTY_FOUR = 44100; - - adjustedAudioFormat = desiredAudioFormat; + const int FORTY_FOUR = 44100; + adjustedAudioFormat = desiredAudioFormat; #ifdef Q_OS_ANDROID - adjustedAudioFormat.setSampleRate(FORTY_FOUR); + adjustedAudioFormat.setSampleRate(FORTY_FOUR); #else - const int HALF_FORTY_FOUR = FORTY_FOUR / 2; + const int HALF_FORTY_FOUR = FORTY_FOUR / 2; - if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) { - // use 48, which is a sample downsample, upsample - adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2); - } else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) { - // use 22050, resample but closer to 24 - adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR); - } else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) { - // use 48000, resample - adjustedAudioFormat.setSampleRate(FORTY_FOUR); - } + if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) { + // use 48, which is a simple downsample, upsample + adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2); + } else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) { + // use 22050, resample but closer to 24 + adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR); + } else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) { + // use 44100, resample + adjustedAudioFormat.setSampleRate(FORTY_FOUR); + } #endif - if (adjustedAudioFormat != desiredAudioFormat) { - // return the nearest in case it needs 2 channels - adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat); - return true; - } else { - return false; - } - } else { - // set the adjustedAudioFormat to the desiredAudioFormat, since it will work - adjustedAudioFormat = desiredAudioFormat; + if (adjustedAudioFormat != desiredAudioFormat) { + // return the nearest in case it needs 2 channels + adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat); return true; + } else { + return false; } } @@ -467,11 +455,6 @@ void AudioClient::stop() { // "switch" to invalid devices in order to shut down the state switchInputToAudioDevice(QAudioDeviceInfo()); switchOutputToAudioDevice(QAudioDeviceInfo()); - - if (_loopbackResampler) { - delete _loopbackResampler; - _loopbackResampler = NULL; - } } void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { @@ -528,6 +511,53 @@ void AudioClient::handleMuteEnvironmentPacket(QSharedPointer me emit muteEnvironmentRequested(position, radius); } +void AudioClient::negotiateAudioFormat() { + auto nodeList = DependencyManager::get(); + auto negotiateFormatPacket = NLPacket::create(PacketType::NegotiateAudioFormat); + auto codecPlugins = PluginManager::getInstance()->getCodecPlugins(); + quint8 numberOfCodecs = (quint8)codecPlugins.size(); + negotiateFormatPacket->writePrimitive(numberOfCodecs); + for (auto& plugin : codecPlugins) { + auto codecName = plugin->getName(); + negotiateFormatPacket->writeString(codecName); + } + + // grab our audio mixer from the NodeList, if it exists + SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); + + if (audioMixer) { + // send off this mute packet + nodeList->sendPacket(std::move(negotiateFormatPacket), *audioMixer); + } +} + +void AudioClient::handleSelectedAudioFormat(QSharedPointer message) { + _selectedCodecName = message->readString(); + + qDebug() << "Selected Codec:" << _selectedCodecName; + + // release any old codec encoder/decoder first... + if (_codec && _encoder) { + _codec->releaseEncoder(_encoder); + _encoder = nullptr; + _codec = nullptr; + } + _receivedAudioStream.cleanupCodec(); + + auto codecPlugins = PluginManager::getInstance()->getCodecPlugins(); + for (auto& plugin : codecPlugins) { + if (_selectedCodecName == plugin->getName()) { + _codec = plugin; + _receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO); + _encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO); + qDebug() << "Selected Codec Plugin:" << _codec.get(); + break; + } + } + +} + + QString AudioClient::getDefaultDeviceName(QAudio::Mode mode) { QAudioDeviceInfo deviceInfo = defaultAudioDeviceForMode(mode); return deviceInfo.deviceName(); @@ -675,16 +705,10 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { } } - // do we need to setup a resampler? - if (_inputFormat.sampleRate() != _outputFormat.sampleRate() && !_loopbackResampler) { - qCDebug(audioclient) << "Attemping to create a resampler for input format to output format for audio loopback."; - - assert(_inputFormat.sampleSize() == 16); - assert(_outputFormat.sampleSize() == 16); - int channelCount = (_inputFormat.channelCount() == 2 && _outputFormat.channelCount() == 2) ? 2 : 1; - - _loopbackResampler = new AudioSRC(_inputFormat.sampleRate(), _outputFormat.sampleRate(), channelCount); - } + // NOTE: we assume the inputFormat and the outputFormat are the same, since on any modern + // multimedia OS they should be. If there is a device that this is not true for, we can + // add back support to do resampling. + Q_ASSERT(_inputFormat.sampleRate() == _outputFormat.sampleRate()); static QByteArray loopBackByteArray; @@ -696,7 +720,8 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { int16_t* inputSamples = reinterpret_cast(inputByteArray.data()); int16_t* loopbackSamples = reinterpret_cast(loopBackByteArray.data()); - possibleResampling(_loopbackResampler, + auto NO_RESAMPLER = nullptr; + possibleResampling(NO_RESAMPLER, inputSamples, loopbackSamples, numInputSamples, numLoopbackSamples, _inputFormat, _outputFormat); @@ -800,7 +825,16 @@ void AudioClient::handleAudioInput() { audioTransform.setTranslation(_positionGetter()); audioTransform.setRotation(_orientationGetter()); // FIXME find a way to properly handle both playback audio and user audio concurrently - emitAudioPacket(networkAudioSamples, numNetworkBytes, _outgoingAvatarAudioSequenceNumber, audioTransform, packetType); + + QByteArray decocedBuffer(reinterpret_cast(networkAudioSamples), numNetworkBytes); + QByteArray encodedBuffer; + if (_encoder) { + _encoder->encode(decocedBuffer, encodedBuffer); + } else { + encodedBuffer = decocedBuffer; + } + + emitAudioPacket(encodedBuffer.constData(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, packetType, _selectedCodecName); _stats.sentPacket(); } } @@ -809,23 +843,114 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) { Transform audioTransform; audioTransform.setTranslation(_positionGetter()); audioTransform.setRotation(_orientationGetter()); + + QByteArray encodedBuffer; + if (_encoder) { + _encoder->encode(audio, encodedBuffer); + } else { + encodedBuffer = audio; + } + // FIXME check a flag to see if we should echo audio? - emitAudioPacket(audio.data(), audio.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, PacketType::MicrophoneAudioWithEcho); + emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, PacketType::MicrophoneAudioWithEcho, _selectedCodecName); } -void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer) { - const int numNetworkOutputSamples = inputBuffer.size() / sizeof(int16_t); - const int numDeviceOutputSamples = numNetworkOutputSamples * (_outputFormat.sampleRate() * _outputFormat.channelCount()) - / (_desiredOutputFormat.sampleRate() * _desiredOutputFormat.channelCount()); +void AudioClient::mixLocalAudioInjectors(int16_t* inputBuffer) { + + memset(_hrtfBuffer, 0, sizeof(_hrtfBuffer)); + QVector injectorsToRemove; + static const float INT16_TO_FLOAT_SCALE_FACTOR = 1/32768.0f; + + bool injectorsHaveData = false; + + // lock the injector vector + Lock lock(_injectorsMutex); + + for (AudioInjector* injector : getActiveLocalAudioInjectors()) { + if (injector->getLocalBuffer()) { + + qint64 samplesToRead = injector->isStereo() ? + AudioConstants::NETWORK_FRAME_BYTES_STEREO : + AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; + + // get one frame from the injector (mono or stereo) + memset(_scratchBuffer, 0, sizeof(_scratchBuffer)); + if (0 < injector->getLocalBuffer()->readData((char*)_scratchBuffer, samplesToRead)) { + + injectorsHaveData = true; + + if (injector->isStereo() ) { + for(int i=0; igetPosition() - _positionGetter(); + float distance = glm::max(glm::length(relativePosition), EPSILON); + float gain = gainForSource(distance, injector->getVolume()); + float azimuth = azimuthForSource(relativePosition); + + injector->getLocalHRTF().render(_scratchBuffer, _hrtfBuffer, 1, azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); + } + + } else { + + qDebug() << "injector has no more data, marking finished for removal"; + injector->finishLocalInjection(); + injectorsToRemove.append(injector); + } + + } else { + + qDebug() << "injector has no local buffer, marking as finished for removal"; + injector->finishLocalInjection(); + injectorsToRemove.append(injector); + } + } + + if(injectorsHaveData) { + + // mix network into the hrtfBuffer + for(int i=0; i(inputBuffer.data()); + const int16_t* decodedSamples; int16_t* outputSamples = reinterpret_cast(outputBuffer.data()); + QByteArray decodedBufferCopy = decodedBuffer; + assert(decodedBuffer.size() == AudioConstants::NETWORK_FRAME_BYTES_STEREO); + + if(getActiveLocalAudioInjectors().size() > 0) { + mixLocalAudioInjectors((int16_t*)decodedBufferCopy.data()); + decodedSamples = reinterpret_cast(decodedBufferCopy.data()); + } else { + decodedSamples = reinterpret_cast(decodedBuffer.data()); + } // copy the packet from the RB to the output - possibleResampling(_networkToOutputResampler, receivedSamples, outputSamples, - numNetworkOutputSamples, numDeviceOutputSamples, + possibleResampling(_networkToOutputResampler, decodedSamples, outputSamples, + numDecodecSamples, numDeviceOutputSamples, _desiredOutputFormat, _outputFormat); // apply stereo reverb at the listener, to the received audio @@ -882,36 +1007,25 @@ void AudioClient::setIsStereoInput(bool isStereoInput) { bool AudioClient::outputLocalInjector(bool isStereo, AudioInjector* injector) { - if (injector->getLocalBuffer()) { - QAudioFormat localFormat = _desiredOutputFormat; - localFormat.setChannelCount(isStereo ? 2 : 1); + Lock lock(_injectorsMutex); + if (injector->getLocalBuffer() && _audioInput ) { + // just add it to the vector of active local injectors, if + // not already there. + // Since this is invoked with invokeMethod, there _should_ be + // no reason to lock access to the vector of injectors. + if (!_activeLocalAudioInjectors.contains(injector)) { + qDebug() << "adding new injector"; + _activeLocalAudioInjectors.append(injector); + } else { + qDebug() << "injector exists in active list already"; + } + + return true; - QAudioOutput* localOutput = new QAudioOutput(getNamedAudioDeviceForMode(QAudio::AudioOutput, _outputAudioDeviceName), - localFormat, - injector->getLocalBuffer()); - - // move the localOutput to the same thread as the local injector buffer - localOutput->moveToThread(injector->getLocalBuffer()->thread()); - - // have it be stopped when that local buffer is about to close - // We don't want to stop this localOutput and injector whenever this AudioClient singleton goes idle, - // only when the localOutput does. But the connection is to localOutput, so that it happens on the right thread. - connect(localOutput, &QAudioOutput::stateChanged, localOutput, [=](QAudio::State state) { - if (state == QAudio::IdleState) { - localOutput->stop(); - injector->stop(); - } - }); - - connect(injector->getLocalBuffer(), &QIODevice::aboutToClose, localOutput, &QAudioOutput::stop); - - qCDebug(audioclient) << "Starting QAudioOutput for local injector" << localOutput; - - localOutput->start(injector->getLocalBuffer()); - return localOutput->state() == QAudio::ActiveState; + } else { + // no local buffer or audio + return false; } - - return false; } void AudioClient::outputFormatChanged() { @@ -1039,12 +1153,6 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice _networkToOutputResampler = NULL; } - if (_loopbackResampler) { - // if we were using an input to output resample, delete it here - delete _loopbackResampler; - _loopbackResampler = NULL; - } - if (!outputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available."; _outputAudioDeviceName = outputDeviceInfo.deviceName().trimmed(); @@ -1170,18 +1278,61 @@ float AudioClient::getAudioOutputMsecsUnplayed() const { return msecsAudioOutputUnplayed; } + +float AudioClient::azimuthForSource(const glm::vec3& relativePosition) { + // copied from AudioMixer, more or less + glm::quat inverseOrientation = glm::inverse(_orientationGetter()); + + // compute sample delay for the 2 ears to create phase panning + glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition; + + // project the rotated source position vector onto x-y plane + rotatedSourcePosition.y = 0.0f; + + static const float SOURCE_DISTANCE_THRESHOLD = 1e-30f; + + if (glm::length2(rotatedSourcePosition) > SOURCE_DISTANCE_THRESHOLD) { + + // produce an oriented angle about the y-axis + return glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedSourcePosition), glm::vec3(0.0f, -1.0f, 0.0f)); + } else { + + // no azimuth if they are in same spot + return 0.0f; + } +} + +float AudioClient::gainForSource(float distance, float volume) { + + const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f; + + // I'm assuming that the AudioMixer's getting of the stream's attenuation + // factor is basically same as getting volume + float gain = volume; + + // attenuate based on distance + if (distance >= ATTENUATION_BEGINS_AT_DISTANCE) { + gain /= (distance/ATTENUATION_BEGINS_AT_DISTANCE); // attenuation = -6dB * log2(distance) + } + + return gain; +} + qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { auto samplesRequested = maxSize / sizeof(int16_t); int samplesPopped; int bytesWritten; - + if ((samplesPopped = _receivedAudioStream.popSamples((int)samplesRequested, false)) > 0) { AudioRingBuffer::ConstIterator lastPopOutput = _receivedAudioStream.getLastPopOutput(); lastPopOutput.readSamples((int16_t*)data, samplesPopped); bytesWritten = samplesPopped * sizeof(int16_t); } else { + // nothing on network, don't grab anything from injectors, and just + // return 0s memset(data, 0, maxSize); bytesWritten = maxSize; + } int bytesAudioOutputUnplayed = _audio->_audioOutput->bufferSize() - _audio->_audioOutput->bytesFree(); @@ -1221,6 +1372,13 @@ void AudioClient::loadSettings() { windowSecondsForDesiredCalcOnTooManyStarves.get()); _receivedAudioStream.setWindowSecondsForDesiredReduction(windowSecondsForDesiredReduction.get()); _receivedAudioStream.setRepetitionWithFade(repetitionWithFade.get()); + + qDebug() << "---- Initializing Audio Client ----"; + auto codecPlugins = PluginManager::getInstance()->getCodecPlugins(); + for (auto& plugin : codecPlugins) { + qDebug() << "Codec available:" << plugin->getName(); + } + } void AudioClient::saveSettings() { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 3a14c878f6..472092163b 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -37,11 +38,17 @@ #include #include #include +#include +#include +#include +#include +#include +#include + +#include #include "AudioIOStats.h" #include "AudioNoiseGate.h" -#include "AudioSRC.h" -#include "AudioReverb.h" #ifdef _WIN32 #pragma warning( push ) @@ -77,6 +84,9 @@ public: using AudioPositionGetter = std::function; using AudioOrientationGetter = std::function; + using Mutex = std::mutex; + using Lock = std::unique_lock; + class AudioOutputIODevice : public QIODevice { public: AudioOutputIODevice(MixedProcessedAudioStream& receivedAudioStream, AudioClient* audio) : @@ -86,7 +96,6 @@ public: void stop() { close(); } qint64 readData(char * data, qint64 maxSize); qint64 writeData(const char * data, qint64 maxSize) { return 0; } - int getRecentUnfulfilledReads() { int unfulfilledReads = _unfulfilledReads; _unfulfilledReads = 0; return unfulfilledReads; } private: MixedProcessedAudioStream& _receivedAudioStream; @@ -94,6 +103,8 @@ public: int _unfulfilledReads; }; + void negotiateAudioFormat(); + const MixedProcessedAudioStream& getReceivedAudioStream() const { return _receivedAudioStream; } MixedProcessedAudioStream& getReceivedAudioStream() { return _receivedAudioStream; } @@ -124,6 +135,8 @@ public: void setPositionGetter(AudioPositionGetter positionGetter) { _positionGetter = positionGetter; } void setOrientationGetter(AudioOrientationGetter orientationGetter) { _orientationGetter = orientationGetter; } + + QVector& getActiveLocalAudioInjectors() { return _activeLocalAudioInjectors; } static const float CALLBACK_ACCELERATOR_RATIO; @@ -139,6 +152,7 @@ public slots: void handleAudioDataPacket(QSharedPointer message); void handleNoisyMutePacket(QSharedPointer message); void handleMuteEnvironmentPacket(QSharedPointer message); + void handleSelectedAudioFormat(QSharedPointer message); void sendDownstreamAudioStatsPacket() { _stats.sendDownstreamAudioStatsPacket(); } void handleAudioInput(); @@ -205,7 +219,11 @@ protected: private: void outputFormatChanged(); + void mixLocalAudioInjectors(int16_t* inputBuffer); + float azimuthForSource(const glm::vec3& relativePosition); + float gainForSource(float distance, float volume); + Mutex _injectorsMutex; QByteArray firstInputFrame; QAudioInput* _audioInput; QAudioFormat _desiredInputFormat; @@ -260,7 +278,11 @@ private: // possible streams needed for resample AudioSRC* _inputToNetworkResampler; AudioSRC* _networkToOutputResampler; - AudioSRC* _loopbackResampler; + + // for local hrtf-ing + float _hrtfBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO]; + int16_t _scratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO]; + AudioLimiter _audioLimiter; // Adds Reverb void configureReverb(); @@ -292,6 +314,12 @@ private: void checkDevices(); bool _hasReceivedFirstPacket = false; + + QVector _activeLocalAudioInjectors; + + CodecPluginPointer _codec; + QString _selectedCodecName; + Encoder* _encoder { nullptr }; // for outbound mic stream }; diff --git a/libraries/audio/CMakeLists.txt b/libraries/audio/CMakeLists.txt index c49c9547a5..1e9360b9a2 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -1,3 +1,3 @@ set(TARGET_NAME audio) setup_hifi_library(Network) -link_hifi_libraries(networking shared) +link_hifi_libraries(networking shared plugins) diff --git a/libraries/audio/src/AbstractAudioInterface.cpp b/libraries/audio/src/AbstractAudioInterface.cpp index b347d57450..bf43c35cb9 100644 --- a/libraries/audio/src/AbstractAudioInterface.cpp +++ b/libraries/audio/src/AbstractAudioInterface.cpp @@ -19,7 +19,8 @@ #include "AudioConstants.h" -void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform, PacketType packetType) { +void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, + const Transform& transform, PacketType packetType, QString codecName) { static std::mutex _mutex; using Locker = std::unique_lock; auto nodeList = DependencyManager::get(); @@ -27,10 +28,17 @@ void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes if (audioMixer && audioMixer->getActiveSocket()) { Locker lock(_mutex); auto audioPacket = NLPacket::create(packetType); + + // FIXME - this is not a good way to determine stereoness with codecs.... quint8 isStereo = bytes == AudioConstants::NETWORK_FRAME_BYTES_STEREO ? 1 : 0; // write sequence number - audioPacket->writePrimitive(sequenceNumber++); + auto sequence = sequenceNumber++; + audioPacket->writePrimitive(sequence); + + // write the codec + audioPacket->writeString(codecName); + if (packetType == PacketType::SilentAudioFrame) { // pack num silent samples quint16 numSilentSamples = isStereo ? @@ -49,8 +57,8 @@ void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes if (audioPacket->getType() != PacketType::SilentAudioFrame) { // audio samples have already been packed (written to networkAudioSamples) - audioPacket->setPayloadSize(audioPacket->getPayloadSize() + bytes); - static const int leadingBytes = sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8); + int leadingBytes = audioPacket->getPayloadSize(); + audioPacket->setPayloadSize(leadingBytes + bytes); memcpy(audioPacket->getPayload() + leadingBytes, audioData, bytes); } nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket); diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index ee52622d7e..223421a7ab 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -28,7 +28,8 @@ class AbstractAudioInterface : public QObject { public: AbstractAudioInterface(QObject* parent = 0) : QObject(parent) {}; - static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform, PacketType packetType); + static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform, + PacketType packetType, QString codecName = QString("")); public slots: virtual bool outputLocalInjector(bool isStereo, AudioInjector* injector) = 0; diff --git a/libraries/audio/src/AudioConstants.h b/libraries/audio/src/AudioConstants.h index 38fead87f1..9271323498 100644 --- a/libraries/audio/src/AudioConstants.h +++ b/libraries/audio/src/AudioConstants.h @@ -18,11 +18,16 @@ namespace AudioConstants { const int SAMPLE_RATE = 24000; + const int MONO = 1; + const int STEREO = 2; + typedef int16_t AudioSample; inline const char* getAudioFrameName() { return "com.highfidelity.recording.Audio"; } + const int MAX_CODEC_NAME_LENGTH = 30; + const int MAX_CODEC_NAME_LENGTH_ON_WIRE = MAX_CODEC_NAME_LENGTH + sizeof(uint32_t); const int NETWORK_FRAME_BYTES_STEREO = 1024; const int NETWORK_FRAME_SAMPLES_STEREO = NETWORK_FRAME_BYTES_STEREO / sizeof(AudioSample); const int NETWORK_FRAME_BYTES_PER_CHANNEL = 512; diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index 7fadf073a1..378e2154c1 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -10,13 +10,19 @@ // #include -#include #include #include #include "AudioHRTF.h" #include "AudioHRTFData.h" +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + // // Equal-gain crossfade // @@ -59,6 +65,103 @@ static const float crossfadeTable[HRTF_BLOCK] = { 0.0024846123f, 0.0019026510f, 0.0013981014f, 0.0009710421f, 0.0006215394f, 0.0003496476f, 0.0001554090f, 0.0000388538f, }; +// +// Model the frequency-dependent attenuation of sound propogation in air. +// +// Fit using linear regression to a log-log model of lowpass cutoff frequency vs distance, +// loosely based on data from Handbook of Acoustics. Only the onset of significant +// attenuation is modelled, not the filter slope. +// +// 1m -> -3dB @ 55kHz +// 10m -> -3dB @ 12kHz +// 100m -> -3dB @ 2.5kHz +// 1km -> -3dB @ 0.6kHz +// 10km -> -3dB @ 0.1kHz +// +static const int NLOWPASS = 64; +static const float lowpassTable[NLOWPASS][5] = { // { b0, b1, b2, a1, a2 } + // distance = 1 + { 0.999772371f, 1.399489756f, 0.454495527f, 1.399458985f, 0.454298669f }, + { 0.999631480f, 1.357609808f, 0.425210203f, 1.357549905f, 0.424901586f }, + { 0.999405154f, 1.311503050f, 0.394349994f, 1.311386830f, 0.393871368f }, + { 0.999042876f, 1.260674595f, 0.361869089f, 1.260450057f, 0.361136504f }, + // distance = 2 + { 0.998465222f, 1.204646525f, 0.327757118f, 1.204214978f, 0.326653886f }, + { 0.997548106f, 1.143019308f, 0.292064663f, 1.142195387f, 0.290436690f }, + { 0.996099269f, 1.075569152f, 0.254941286f, 1.074009405f, 0.252600301f }, + { 0.993824292f, 1.002389610f, 0.216688640f, 0.999469185f, 0.213433357f }, + // distance = 4 + { 0.990280170f, 0.924075266f, 0.177827150f, 0.918684864f, 0.173497723f }, + { 0.984818279f, 0.841917936f, 0.139164195f, 0.832151968f, 0.133748443f }, + { 0.976528670f, 0.758036513f, 0.101832398f, 0.740761682f, 0.095635899f }, + { 0.964216485f, 0.675305244f, 0.067243474f, 0.645654855f, 0.061110348f }, + // distance = 8 + { 0.946463038f, 0.596943020f, 0.036899688f, 0.547879974f, 0.032425772f }, + { 0.921823868f, 0.525770189f, 0.012060451f, 0.447952111f, 0.011702396f }, + { 0.890470015f, 0.463334299f, -0.001227816f, 0.347276405f, 0.005300092f }, + { 0.851335343f, 0.407521164f, -0.009353968f, 0.241900234f, 0.007602305f }, + // distance = 16 + { 0.804237360f, 0.358139558f, -0.014293332f, 0.130934213f, 0.017149373f }, + { 0.750073259f, 0.314581568f, -0.016625381f, 0.014505388f, 0.033524057f }, + { 0.690412072f, 0.275936128f, -0.017054561f, -0.106682490f, 0.055976129f }, + { 0.627245545f, 0.241342015f, -0.016246850f, -0.231302564f, 0.083643275f }, + // distance = 32 + { 0.562700627f, 0.210158533f, -0.014740899f, -0.357562697f, 0.115680957f }, + { 0.498787849f, 0.181982455f, -0.012925406f, -0.483461730f, 0.151306628f }, + { 0.437224055f, 0.156585449f, -0.011055180f, -0.607042210f, 0.189796534f }, + { 0.379336998f, 0.133834032f, -0.009281617f, -0.726580065f, 0.230469477f }, + // distance = 64 + { 0.326040627f, 0.113624970f, -0.007683443f, -0.840693542f, 0.272675696f }, + { 0.277861727f, 0.095845793f, -0.006291936f, -0.948380091f, 0.315795676f }, + { 0.234997480f, 0.080357656f, -0.005109519f, -1.049001190f, 0.359246807f }, + { 0.197386484f, 0.066993521f, -0.004122547f, -1.142236313f, 0.402493771f }, + // distance = 128 + { 0.164780457f, 0.055564709f, -0.003309645f, -1.228023442f, 0.445058962f }, + { 0.136808677f, 0.045870650f, -0.002646850f, -1.306498037f, 0.486530514f }, + { 0.113031290f, 0.037708627f, -0.002110591f, -1.377937457f, 0.526566783f }, + { 0.092980475f, 0.030881892f, -0.001679255f, -1.442713983f, 0.564897095f }, + // distance = 256 + { 0.076190239f, 0.025205585f, -0.001333863f, -1.501257246f, 0.601319206f }, + { 0.062216509f, 0.020510496f, -0.001058229f, -1.554025452f, 0.635694228f }, + { 0.050649464f, 0.016644994f, -0.000838826f, -1.601484205f, 0.667939837f }, + { 0.041120009f, 0.013475547f, -0.000664513f, -1.644091518f, 0.698022561f }, + // distance = 512 + { 0.033302044f, 0.010886252f, -0.000526217f, -1.682287704f, 0.725949783f }, + { 0.026911868f, 0.008777712f, -0.000416605f, -1.716488979f, 0.751761953f }, + { 0.021705773f, 0.007065551f, -0.000329788f, -1.747083800f, 0.775525335f }, + { 0.017476603f, 0.005678758f, -0.000261057f, -1.774431204f, 0.797325509f }, + // distance = 1024 + { 0.014049828f, 0.004558012f, -0.000206658f, -1.798860530f, 0.817261711f }, + { 0.011279504f, 0.003654067f, -0.000163610f, -1.820672082f, 0.835442043f }, + { 0.009044384f, 0.002926264f, -0.000129544f, -1.840138412f, 0.851979516f }, + { 0.007244289f, 0.002341194f, -0.000102586f, -1.857505967f, 0.866988864f }, + // distance = 2048 + { 0.005796846f, 0.001871515f, -0.000081250f, -1.872996926f, 0.880584038f }, + { 0.004634607f, 0.001494933f, -0.000064362f, -1.886811124f, 0.892876302f }, + { 0.003702543f, 0.001193324f, -0.000050993f, -1.899127955f, 0.903972829f }, + { 0.002955900f, 0.000951996f, -0.000040407f, -1.910108223f, 0.913975712f }, + // distance = 4096 + { 0.002358382f, 0.000759068f, -0.000032024f, -1.919895894f, 0.922981321f }, + { 0.001880626f, 0.000604950f, -0.000025383f, -1.928619738f, 0.931079931f }, + { 0.001498926f, 0.000481920f, -0.000020123f, -1.936394836f, 0.938355560f }, + { 0.001194182f, 0.000383767f, -0.000015954f, -1.943323983f, 0.944885977f }, + // distance = 8192 + { 0.000951028f, 0.000305502f, -0.000012651f, -1.949498943f, 0.950742822f }, + { 0.000757125f, 0.000243126f, -0.000010033f, -1.955001608f, 0.955991826f }, + { 0.000602572f, 0.000193434f, -0.000007957f, -1.959905036f, 0.960693085f }, + { 0.000479438f, 0.000153861f, -0.000006312f, -1.964274383f, 0.964901371f }, + // distance = 16384 + { 0.000381374f, 0.000122359f, -0.000005007f, -1.968167752f, 0.968666478f }, + { 0.000303302f, 0.000097288f, -0.000003972f, -1.971636944f, 0.972033562f }, + { 0.000241166f, 0.000077342f, -0.000003151f, -1.974728138f, 0.975043493f }, + { 0.000191726f, 0.000061475f, -0.000002500f, -1.977482493f, 0.977733194f }, + // distance = 32768 + { 0.000152399f, 0.000048857f, -0.000001984f, -1.979936697f, 0.980135969f }, + { 0.000121122f, 0.000038825f, -0.000001574f, -1.982123446f, 0.982281818f }, + { 0.000096252f, 0.000030849f, -0.000001249f, -1.984071877f, 0.984197728f }, + { 0.000076480f, 0.000024509f, -0.000000991f, -1.985807957f, 0.985907955f }, +}; + static const float TWOPI = 6.283185307f; // @@ -119,68 +222,18 @@ static void FIR_1x4_SSE(float* src, float* dst0, float* dst1, float* dst2, float } } -// -// Detect AVX/AVX2 support -// - -#if defined(_MSC_VER) - -#include - -static bool cpuSupportsAVX() { - int info[4]; - int mask = (1 << 27) | (1 << 28); // OSXSAVE and AVX - - __cpuidex(info, 0x1, 0); - - bool result = false; - if ((info[2] & mask) == mask) { - - if ((_xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) { - result = true; - } - } - return result; -} - -#elif defined(__GNUC__) - -#include - -static bool cpuSupportsAVX() { - unsigned int eax, ebx, ecx, edx; - unsigned int mask = (1 << 27) | (1 << 28); // OSXSAVE and AVX - - bool result = false; - if (__get_cpuid(0x1, &eax, &ebx, &ecx, &edx) && ((ecx & mask) == mask)) { - - __asm__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0)); - if ((eax & 0x6) == 0x6) { - result = true; - } - } - return result; -} - -#else - -static bool cpuSupportsAVX() { - return false; -} - -#endif - // // Runtime CPU dispatch // -typedef void FIR_1x4_t(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); -FIR_1x4_t FIR_1x4_AVX; // separate compilation with VEX-encoding enabled +#include "CPUDetect.h" + +void FIR_1x4_AVX(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) { - static FIR_1x4_t* f = cpuSupportsAVX() ? FIR_1x4_AVX : FIR_1x4_SSE; // init on first call - (*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch + static auto f = cpuSupportsAVX() ? FIR_1x4_AVX : FIR_1x4_SSE; + (*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch } // 4 channel planar to interleaved @@ -213,40 +266,68 @@ static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, f } } -// 4 channels (interleaved) -static void biquad_4x4(float* src, float* dst, float coef[5][4], float state[2][4], int numFrames) { +// process 2 cascaded biquads on 4 channels (interleaved) +// biquads computed in parallel, by adding one sample of delay +static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { // enable flush-to-zero mode to prevent denormals unsigned int ftz = _MM_GET_FLUSH_ZERO_MODE(); _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); - __m128 w1 = _mm_loadu_ps(state[0]); - __m128 w2 = _mm_loadu_ps(state[1]); + // restore state + __m128 y00 = _mm_loadu_ps(&state[0][0]); + __m128 w10 = _mm_loadu_ps(&state[1][0]); + __m128 w20 = _mm_loadu_ps(&state[2][0]); - __m128 b0 = _mm_loadu_ps(coef[0]); - __m128 b1 = _mm_loadu_ps(coef[1]); - __m128 b2 = _mm_loadu_ps(coef[2]); - __m128 a1 = _mm_loadu_ps(coef[3]); - __m128 a2 = _mm_loadu_ps(coef[4]); + __m128 y01; + __m128 w11 = _mm_loadu_ps(&state[1][4]); + __m128 w21 = _mm_loadu_ps(&state[2][4]); + + // first biquad coefs + __m128 b00 = _mm_loadu_ps(&coef[0][0]); + __m128 b10 = _mm_loadu_ps(&coef[1][0]); + __m128 b20 = _mm_loadu_ps(&coef[2][0]); + __m128 a10 = _mm_loadu_ps(&coef[3][0]); + __m128 a20 = _mm_loadu_ps(&coef[4][0]); + + // second biquad coefs + __m128 b01 = _mm_loadu_ps(&coef[0][4]); + __m128 b11 = _mm_loadu_ps(&coef[1][4]); + __m128 b21 = _mm_loadu_ps(&coef[2][4]); + __m128 a11 = _mm_loadu_ps(&coef[3][4]); + __m128 a21 = _mm_loadu_ps(&coef[4][4]); for (int i = 0; i < numFrames; i++) { + __m128 x00 = _mm_loadu_ps(&src[4*i]); + __m128 x01 = y00; // first biquad output + // transposed Direct Form II - __m128 x0 = _mm_loadu_ps(&src[4*i]); - __m128 y0; + y00 = _mm_add_ps(w10, _mm_mul_ps(x00, b00)); + y01 = _mm_add_ps(w11, _mm_mul_ps(x01, b01)); - y0 = _mm_add_ps(w1, _mm_mul_ps(x0, b0)); - w1 = _mm_add_ps(w2, _mm_mul_ps(x0, b1)); - w2 = _mm_mul_ps(x0, b2); - w1 = _mm_sub_ps(w1, _mm_mul_ps(y0, a1)); - w2 = _mm_sub_ps(w2, _mm_mul_ps(y0, a2)); + w10 = _mm_add_ps(w20, _mm_mul_ps(x00, b10)); + w11 = _mm_add_ps(w21, _mm_mul_ps(x01, b11)); - _mm_storeu_ps(&dst[4*i], y0); + w20 = _mm_mul_ps(x00, b20); + w21 = _mm_mul_ps(x01, b21); + + w10 = _mm_sub_ps(w10, _mm_mul_ps(y00, a10)); + w11 = _mm_sub_ps(w11, _mm_mul_ps(y01, a11)); + + w20 = _mm_sub_ps(w20, _mm_mul_ps(y00, a20)); + w21 = _mm_sub_ps(w21, _mm_mul_ps(y01, a21)); + + _mm_storeu_ps(&dst[4*i], y01); // second biquad output } // save state - _mm_storeu_ps(state[0], w1); - _mm_storeu_ps(state[1], w2); + _mm_storeu_ps(&state[0][0], y00); + _mm_storeu_ps(&state[1][0], w10); + _mm_storeu_ps(&state[2][0], w20); + + _mm_storeu_ps(&state[1][4], w11); + _mm_storeu_ps(&state[2][4], w21); _MM_SET_FLUSH_ZERO_MODE(ftz); } @@ -396,56 +477,105 @@ static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, f } } -// 4 channels (interleaved) -static void biquad_4x4(float* src, float* dst, float coef[5][4], float state[2][4], int numFrames) { +// process 2 cascaded biquads on 4 channels (interleaved) +// biquads are computed in parallel, by adding one sample of delay +static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { - // channel 0 - float w10 = state[0][0]; - float w20 = state[1][0]; + // restore state + float y00 = state[0][0]; + float w10 = state[1][0]; + float w20 = state[2][0]; + float y01 = state[0][1]; + float w11 = state[1][1]; + float w21 = state[2][1]; + + float y02 = state[0][2]; + float w12 = state[1][2]; + float w22 = state[2][2]; + + float y03 = state[0][3]; + float w13 = state[1][3]; + float w23 = state[2][3]; + + float y04; + float w14 = state[1][4]; + float w24 = state[2][4]; + + float y05; + float w15 = state[1][5]; + float w25 = state[2][5]; + + float y06; + float w16 = state[1][6]; + float w26 = state[2][6]; + + float y07; + float w17 = state[1][7]; + float w27 = state[2][7]; + + // first biquad coefs float b00 = coef[0][0]; float b10 = coef[1][0]; float b20 = coef[2][0]; float a10 = coef[3][0]; float a20 = coef[4][0]; - // channel 1 - float w11 = state[0][1]; - float w21 = state[1][1]; - float b01 = coef[0][1]; float b11 = coef[1][1]; float b21 = coef[2][1]; float a11 = coef[3][1]; float a21 = coef[4][1]; - // channel 2 - float w12 = state[0][2]; - float w22 = state[1][2]; - float b02 = coef[0][2]; float b12 = coef[1][2]; float b22 = coef[2][2]; float a12 = coef[3][2]; float a22 = coef[4][2]; - // channel 3 - float w13 = state[0][3]; - float w23 = state[1][3]; - float b03 = coef[0][3]; float b13 = coef[1][3]; float b23 = coef[2][3]; float a13 = coef[3][3]; float a23 = coef[4][3]; + // second biquad coefs + float b04 = coef[0][4]; + float b14 = coef[1][4]; + float b24 = coef[2][4]; + float a14 = coef[3][4]; + float a24 = coef[4][4]; + + float b05 = coef[0][5]; + float b15 = coef[1][5]; + float b25 = coef[2][5]; + float a15 = coef[3][5]; + float a25 = coef[4][5]; + + float b06 = coef[0][6]; + float b16 = coef[1][6]; + float b26 = coef[2][6]; + float a16 = coef[3][6]; + float a26 = coef[4][6]; + + float b07 = coef[0][7]; + float b17 = coef[1][7]; + float b27 = coef[2][7]; + float a17 = coef[3][7]; + float a27 = coef[4][7]; + for (int i = 0; i < numFrames; i++) { + // first biquad input float x00 = src[4*i+0] + 1.0e-20f; // prevent denormals float x01 = src[4*i+1] + 1.0e-20f; float x02 = src[4*i+2] + 1.0e-20f; float x03 = src[4*i+3] + 1.0e-20f; - float y00, y01, y02, y03; + // second biquad input is previous output + float x04 = y00; + float x05 = y01; + float x06 = y02; + float x07 = y03; // transposed Direct Form II y00 = b00 * x00 + w10; @@ -464,24 +594,57 @@ static void biquad_4x4(float* src, float* dst, float coef[5][4], float state[2][ w13 = b13 * x03 - a13 * y03 + w23; w23 = b23 * x03 - a23 * y03; - dst[4*i+0] = y00; - dst[4*i+1] = y01; - dst[4*i+2] = y02; - dst[4*i+3] = y03; + // transposed Direct Form II + y04 = b04 * x04 + w14; + w14 = b14 * x04 - a14 * y04 + w24; + w24 = b24 * x04 - a24 * y04; + + y05 = b05 * x05 + w15; + w15 = b15 * x05 - a15 * y05 + w25; + w25 = b25 * x05 - a25 * y05; + + y06 = b06 * x06 + w16; + w16 = b16 * x06 - a16 * y06 + w26; + w26 = b26 * x06 - a26 * y06; + + y07 = b07 * x07 + w17; + w17 = b17 * x07 - a17 * y07 + w27; + w27 = b27 * x07 - a27 * y07; + + dst[4*i+0] = y04; // second biquad output + dst[4*i+1] = y05; + dst[4*i+2] = y06; + dst[4*i+3] = y07; } // save state - state[0][0] = w10; - state[1][0] = w20; + state[0][0] = y00; + state[1][0] = w10; + state[2][0] = w20; - state[0][1] = w11; - state[1][1] = w21; + state[0][1] = y01; + state[1][1] = w11; + state[2][1] = w21; - state[0][2] = w12; - state[1][2] = w22; + state[0][2] = y02; + state[1][2] = w12; + state[2][2] = w22; - state[0][3] = w13; - state[1][3] = w23; + state[0][3] = y03; + state[1][3] = w13; + state[2][3] = w23; + + state[1][4] = w14; + state[2][4] = w24; + + state[1][5] = w15; + state[2][5] = w25; + + state[1][6] = w16; + state[2][6] = w26; + + state[1][7] = w17; + state[2][7] = w27; } // crossfade 4 inputs into 2 outputs with accumulation (interleaved) @@ -519,9 +682,63 @@ static void ThiranBiquad(float f, float& b0, float& b1, float& b2, float& a1, fl b2 = 1.0f; } -// compute new filters for a given azimuth and gain -static void setAzimuthAndGain(float firCoef[4][HRTF_TAPS], float bqCoef[5][4], int delay[4], - int index, float azimuth, float gain, int channel) { +// split x into exponent and fraction (0.0f to 1.0f) +static void splitf(float x, int& expn, float& frac) { + + union { float f; int i; } mant, bits = { x }; + const int IEEE754_MANT_BITS = 23; + const int IEEE754_EXPN_BIAS = 127; + + mant.i = bits.i & ((1 << IEEE754_MANT_BITS) - 1); + mant.i |= (IEEE754_EXPN_BIAS << IEEE754_MANT_BITS); + + frac = mant.f - 1.0f; + expn = (bits.i >> IEEE754_MANT_BITS) - IEEE754_EXPN_BIAS; +} + +static void distanceBiquad(float distance, float& b0, float& b1, float& b2, float& a1, float& a2) { + + // + // Computed from a lookup table quantized to distance = 2^(N/4) + // and reconstructed by piecewise linear interpolation. + // Approximation error < 0.25dB + // + + float x = distance; + x = MIN(MAX(x, 1.0f), 1<<30); + x *= x; + x *= x; // x = distance^4 + + // split x into e and frac, such that x = 2^(e+0) + frac * (2^(e+1) - 2^(e+0)) + int e; + float frac; + splitf(x, e, frac); + + // clamp to table limits + if (e < 0) { + e = 0; + frac = 0.0f; + } + if (e > NLOWPASS-2) { + e = NLOWPASS-2; + frac = 1.0f; + } + assert(frac >= 0.0f); + assert(frac <= 1.0f); + assert(e+0 >= 0); + assert(e+1 < NLOWPASS); + + // piecewise linear interpolation + b0 = lowpassTable[e+0][0] + frac * (lowpassTable[e+1][0] - lowpassTable[e+0][0]); + b1 = lowpassTable[e+0][1] + frac * (lowpassTable[e+1][1] - lowpassTable[e+0][1]); + b2 = lowpassTable[e+0][2] + frac * (lowpassTable[e+1][2] - lowpassTable[e+0][2]); + a1 = lowpassTable[e+0][3] + frac * (lowpassTable[e+1][3] - lowpassTable[e+0][3]); + a2 = lowpassTable[e+0][4] + frac * (lowpassTable[e+1][4] - lowpassTable[e+0][4]); +} + +// compute new filters for a given azimuth, distance and gain +static void setFilters(float firCoef[4][HRTF_TAPS], float bqCoef[5][8], int delay[4], + int index, float azimuth, float distance, float gain, int channel) { // convert from radians to table units azimuth *= HRTF_AZIMUTHS / TWOPI; @@ -602,9 +819,26 @@ static void setAzimuthAndGain(float firCoef[4][HRTF_TAPS], float bqCoef[5][4], i bqCoef[4][channel+1] = a2; delay[channel+1] = itdi; } + + // + // Second biquad implements the distance filter. + // + distanceBiquad(distance, b0, b1, b2, a1, a2); + + bqCoef[0][channel+4] = b0; + bqCoef[1][channel+4] = b1; + bqCoef[2][channel+4] = b2; + bqCoef[3][channel+4] = a1; + bqCoef[4][channel+4] = a2; + + bqCoef[0][channel+5] = b0; + bqCoef[1][channel+5] = b1; + bqCoef[2][channel+5] = b2; + bqCoef[3][channel+5] = a1; + bqCoef[4][channel+5] = a2; } -void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, float gain, int numFrames) { +void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames) { assert(index >= 0); assert(index < HRTF_TABLES); @@ -613,18 +847,19 @@ void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, float in[HRTF_TAPS + HRTF_BLOCK]; // mono float firCoef[4][HRTF_TAPS]; // 4-channel float firBuffer[4][HRTF_DELAY + HRTF_BLOCK]; // 4-channel - float bqCoef[5][4]; // 4-channel (interleaved) + float bqCoef[5][8]; // 4-channel (interleaved) float bqBuffer[4 * HRTF_BLOCK]; // 4-channel (interleaved) int delay[4]; // 4-channel (interleaved) // to avoid polluting the cache, old filters are recomputed instead of stored - setAzimuthAndGain(firCoef, bqCoef, delay, index, _azimuthState, _gainState, L0); + setFilters(firCoef, bqCoef, delay, index, _azimuthState, _distanceState, _gainState, L0); // compute new filters - setAzimuthAndGain(firCoef, bqCoef, delay, index, azimuth, gain, L1); + setFilters(firCoef, bqCoef, delay, index, azimuth, distance, gain, L1); // new parameters become old _azimuthState = azimuth; + _distanceState = distance; _gainState = gain; // convert mono input to float @@ -662,14 +897,25 @@ void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, &firBuffer[R1][HRTF_DELAY] - delay[R1], bqBuffer, HRTF_BLOCK); - // process old/new fractional delay - biquad_4x4(bqBuffer, bqBuffer, bqCoef, _bqState, HRTF_BLOCK); + // process old/new biquads + biquad2_4x4(bqBuffer, bqBuffer, bqCoef, _bqState, HRTF_BLOCK); // new state becomes old _bqState[0][L0] = _bqState[0][L1]; _bqState[1][L0] = _bqState[1][L1]; + _bqState[2][L0] = _bqState[2][L1]; + _bqState[0][R0] = _bqState[0][R1]; _bqState[1][R0] = _bqState[1][R1]; + _bqState[2][R0] = _bqState[2][R1]; + + _bqState[0][L2] = _bqState[0][L3]; + _bqState[1][L2] = _bqState[1][L3]; + _bqState[2][L2] = _bqState[2][L3]; + + _bqState[0][R2] = _bqState[0][R3]; + _bqState[1][R2] = _bqState[1][R3]; + _bqState[2][R2] = _bqState[2][R3]; // crossfade old/new output and accumulate crossfade_4x2(bqBuffer, output, crossfadeTable, HRTF_BLOCK); @@ -677,15 +923,16 @@ void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, _silentState = false; } -void AudioHRTF::renderSilent(int16_t* input, float* output, int index, float azimuth, float gain, int numFrames) { +void AudioHRTF::renderSilent(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames) { // process the first silent block, to flush internal state if (!_silentState) { - render(input, output, index, azimuth, gain, numFrames); + render(input, output, index, azimuth, distance, gain, numFrames); } // new parameters become old _azimuthState = azimuth; + _distanceState = distance; _gainState = gain; _silentState = true; diff --git a/libraries/audio/src/AudioHRTF.h b/libraries/audio/src/AudioHRTF.h index f92f9c1602..63d1712980 100644 --- a/libraries/audio/src/AudioHRTF.h +++ b/libraries/audio/src/AudioHRTF.h @@ -21,7 +21,7 @@ static const int HRTF_TABLES = 25; // number of HRTF subjects static const int HRTF_DELAY = 24; // max ITD in samples (1.0ms at 24KHz) static const int HRTF_BLOCK = 256; // block processing size -static const float HRTF_GAIN = 0.5f; // HRTF global gain adjustment +static const float HRTF_GAIN = 1.0f; // HRTF global gain adjustment class AudioHRTF { @@ -32,16 +32,17 @@ public: // input: mono source // output: interleaved stereo mix buffer (accumulates into existing output) // index: HRTF subject index - // azimuth: clockwise panning angle [0, 360] in degrees + // azimuth: clockwise panning angle in radians + // distance: source distance in meters // gain: gain factor for distance attenuation // numFrames: must be HRTF_BLOCK in this version // - void render(int16_t* input, float* output, int index, float azimuth, float gain, int numFrames); + void render(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames); // // Fast path when input is known to be silent // - void renderSilent(int16_t* input, float* output, int index, float azimuth, float gain, int numFrames); + void renderSilent(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames); private: AudioHRTF(const AudioHRTF&) = delete; @@ -49,10 +50,10 @@ private: // SIMD channel assignmentS enum Channel { - L0, - R0, - L1, - R1 + L0, R0, + L1, R1, + L2, R2, + L3, R3 }; // For best cache utilization when processing thousands of instances, only @@ -64,11 +65,12 @@ private: // integer delay history float _delayState[4][HRTF_DELAY] = {}; - // fractional delay history - float _bqState[2][4] = {}; + // biquad history + float _bqState[3][8] = {}; // parameter history float _azimuthState = 0.0f; + float _distanceState = 0.0f; float _gainState = 0.0f; bool _silentState = false; diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 878a4c627c..873e9b7923 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -26,6 +26,17 @@ #include "AudioInjector.h" +int audioInjectorPtrMetaTypeId = qRegisterMetaType(); + +AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +}; + +AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs) { + lhs = static_cast(static_cast(lhs) | static_cast(rhs)); + return lhs; +}; + AudioInjector::AudioInjector(QObject* parent) : QObject(parent) { @@ -41,14 +52,42 @@ AudioInjector::AudioInjector(const Sound& sound, const AudioInjectorOptions& inj AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions) : _audioData(audioData), - _options(injectorOptions) + _options(injectorOptions) { } +bool AudioInjector::stateHas(AudioInjectorState state) const { + return (_state & state) == state; +} + +void AudioInjector::setOptions(const AudioInjectorOptions& options) { + // since options.stereo is computed from the audio stream, + // we need to copy it from existing options just in case. + bool currentlyStereo = _options.stereo; + _options = options; + _options.stereo = currentlyStereo; +} + +void AudioInjector::finishNetworkInjection() { + _state |= AudioInjectorState::NetworkInjectionFinished; + + // if we are already finished with local + // injection, then we are finished + if(stateHas(AudioInjectorState::LocalInjectionFinished)) { + finish(); + } +} + +void AudioInjector::finishLocalInjection() { + _state |= AudioInjectorState::LocalInjectionFinished; + if(_options.localOnly || stateHas(AudioInjectorState::NetworkInjectionFinished)) { + finish(); + } +} + void AudioInjector::finish() { - bool shouldDelete = (_state == State::NotFinishedWithPendingDelete); - _state = State::Finished; + _state |= AudioInjectorState::Finished; emit finished(); @@ -58,7 +97,7 @@ void AudioInjector::finish() { _localBuffer = NULL; } - if (shouldDelete) { + if (stateHas(AudioInjectorState::PendingDelete)) { // we've been asked to delete after finishing, trigger a deleteLater here deleteLater(); } @@ -110,23 +149,27 @@ void AudioInjector::restart() { _hasSentFirstFrame = false; // check our state to decide if we need extra handling for the restart request - if (_state == State::Finished) { + if (stateHas(AudioInjectorState::Finished)) { // we finished playing, need to reset state so we can get going again _hasSetup = false; _shouldStop = false; - _state = State::NotFinished; - + _state = AudioInjectorState::NotFinished; + // call inject audio to start injection over again setupInjection(); - // if we're a local injector call inject locally to start injecting again - if (_options.localOnly) { - injectLocally(); - } else { - // wake the AudioInjectorManager back up if it's stuck waiting - if (!injectorManager->restartFinishedInjector(this)) { - _state = State::Finished; // we're not playing, so reset the state used by isPlaying. + // inject locally + if(injectLocally()) { + + // if not localOnly, wake the AudioInjectorManager back up if it is stuck waiting + if (!_options.localOnly) { + + if (!injectorManager->restartFinishedInjector(this)) { + _state = AudioInjectorState::Finished; // we're not playing, so reset the state used by isPlaying. + } } + } else { + _state = AudioInjectorState::Finished; // we failed to play, so we are finished again } } } @@ -145,7 +188,8 @@ bool AudioInjector::injectLocally() { // give our current send position to the local buffer _localBuffer->setCurrentOffset(_currentSendOffset); - success = _localAudioInterface->outputLocalInjector(_options.stereo, this); + // call this function on the AudioClient's thread + success = QMetaObject::invokeMethod(_localAudioInterface, "outputLocalInjector", Q_ARG(bool, _options.stereo), Q_ARG(AudioInjector*, this)); if (!success) { qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface"; @@ -170,8 +214,16 @@ const uchar MAX_INJECTOR_VOLUME = 0xFF; static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1; static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0; +qint64 writeStringToStream(const QString& string, QDataStream& stream) { + QByteArray data = string.toUtf8(); + uint32_t length = data.length(); + stream << static_cast(length); + stream << data; + return length + sizeof(uint32_t); +} + int64_t AudioInjector::injectNextFrame() { - if (_state == AudioInjector::State::Finished) { + if (stateHas(AudioInjectorState::NetworkInjectionFinished)) { qDebug() << "AudioInjector::injectNextFrame called but AudioInjector has finished and was not restarted. Returning."; return NEXT_FRAME_DELTA_ERROR_OR_FINISHED; } @@ -216,14 +268,20 @@ int64_t AudioInjector::injectNextFrame() { // pack some placeholder sequence number for now audioPacketStream << (quint16) 0; + // current injectors don't use codecs, so pack in the unknown codec name + QString noCodecForInjectors(""); + writeStringToStream(noCodecForInjectors, audioPacketStream); + // pack stream identifier (a generated UUID) audioPacketStream << QUuid::createUuid(); // pack the stereo/mono type of the stream audioPacketStream << _options.stereo; - // pack the flag for loopback - uchar loopbackFlag = (uchar)true; + // pack the flag for loopback. Now, we don't loopback + // and _always_ play locally, so loopbackFlag should be + // false always. + uchar loopbackFlag = (uchar)false; audioPacketStream << loopbackFlag; // pack the position for injected audio @@ -289,16 +347,23 @@ int64_t AudioInjector::injectNextFrame() { _currentPacket->seek(audioDataOffset); + // This code is copying bytes from the _audioData directly into the packet, handling looping appropriately. + // Might be a reasonable place to do the encode step here. + QByteArray decodedAudio; while (totalBytesLeftToCopy > 0) { int bytesToCopy = std::min(totalBytesLeftToCopy, _audioData.size() - _currentSendOffset); - _currentPacket->write(_audioData.data() + _currentSendOffset, bytesToCopy); + decodedAudio.append(_audioData.data() + _currentSendOffset, bytesToCopy); _currentSendOffset += bytesToCopy; totalBytesLeftToCopy -= bytesToCopy; if (_options.loop && _currentSendOffset >= _audioData.size()) { _currentSendOffset = 0; } } + // FIXME -- good place to call codec encode here. We need to figure out how to tell the AudioInjector which + // codec to use... possible through AbstractAudioInterface. + QByteArray encodedAudio = decodedAudio; + _currentPacket->write(encodedAudio.data(), encodedAudio.size()); // set the correct size used for this packet _currentPacket->setPayloadSize(_currentPacket->pos()); @@ -314,7 +379,7 @@ int64_t AudioInjector::injectNextFrame() { } if (_currentSendOffset >= _audioData.size() && !_options.loop) { - finish(); + finishNetworkInjection(); return NEXT_FRAME_DELTA_ERROR_OR_FINISHED; } @@ -353,10 +418,10 @@ void AudioInjector::triggerDeleteAfterFinish() { return; } - if (_state == State::Finished) { + if (_state == AudioInjectorState::Finished) { stopAndDeleteLater(); } else { - _state = State::NotFinishedWithPendingDelete; + _state |= AudioInjectorState::PendingDelete; } } @@ -402,7 +467,7 @@ AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjector* sound = playSound(buffer, options, localInterface); if (sound) { - sound->_state = AudioInjector::State::NotFinishedWithPendingDelete; + sound->_state |= AudioInjectorState::PendingDelete; } return sound; @@ -419,21 +484,23 @@ AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInj // setup parameters required for injection injector->setupInjection(); - if (options.localOnly) { - if (injector->injectLocally()) { - // local injection succeeded, return the pointer to injector - return injector; - } else { - // unable to inject locally, return a nullptr - return nullptr; - } - } else { - // attempt to thread the new injector - if (injectorManager->threadInjector(injector)) { - return injector; - } else { - // we failed to thread the new injector (we are at the max number of injector threads) - return nullptr; - } + // we always inject locally + // + if (!injector->injectLocally()) { + // failed, so don't bother sending to server + qDebug() << "AudioInjector::playSound failed to inject locally"; + return nullptr; } + // if localOnly, we are done, just return injector. + if (options.localOnly) { + return injector; + } + + // send off to server for everyone else + if (!injectorManager->threadInjector(injector)) { + // we failed to thread the new injector (we are at the max number of injector threads) + qDebug() << "AudioInjector::playSound failed to thread injector"; + } + return injector; + } diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index c90256429d..b872800e15 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -26,38 +26,49 @@ #include "AudioInjectorLocalBuffer.h" #include "AudioInjectorOptions.h" +#include "AudioHRTF.h" #include "Sound.h" class AbstractAudioInterface; class AudioInjectorManager; + +enum class AudioInjectorState : uint8_t { + NotFinished = 0, + Finished = 1, + PendingDelete = 2, + LocalInjectionFinished = 4, + NetworkInjectionFinished = 8 +}; + +AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs); +AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs); + // In order to make scripting cleaner for the AudioInjector, the script now holds on to the AudioInjector object // until it dies. - class AudioInjector : public QObject { Q_OBJECT public: - enum class State : uint8_t { - NotFinished, - NotFinishedWithPendingDelete, - Finished - }; - AudioInjector(QObject* parent); AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions); AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions); - bool isFinished() const { return _state == State::Finished; } + bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); } int getCurrentSendOffset() const { return _currentSendOffset; } void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; } AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; } + AudioHRTF& getLocalHRTF() { return _localHRTF; } + bool isLocalOnly() const { return _options.localOnly; } - + float getVolume() const { return _options.volume; } + glm::vec3 getPosition() const { return _options.position; } + bool isStereo() const { return _options.stereo; } void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; } + bool stateHas(AudioInjectorState state) const ; static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); static AudioInjector* playSound(SharedSoundPointer sound, const float volume, const float stretchFactor, const glm::vec3 position); @@ -70,18 +81,18 @@ public slots: void stopAndDeleteLater(); const AudioInjectorOptions& getOptions() const { return _options; } - void setOptions(const AudioInjectorOptions& options) { _options = options; } + void setOptions(const AudioInjectorOptions& options); float getLoudness() const { return _loudness; } - bool isPlaying() const { return _state == State::NotFinished || _state == State::NotFinishedWithPendingDelete; } + bool isPlaying() const { return !stateHas(AudioInjectorState::Finished); } + void finish(); + void finishLocalInjection(); + void finishNetworkInjection(); signals: void finished(); void restarting(); -private slots: - void finish(); - private: void setupInjection(); int64_t injectNextFrame(); @@ -89,7 +100,7 @@ private: QByteArray _audioData; AudioInjectorOptions _options; - State _state { State::NotFinished }; + AudioInjectorState _state { AudioInjectorState::NotFinished }; bool _hasSentFirstFrame { false }; bool _hasSetup { false }; bool _shouldStop { false }; @@ -103,8 +114,10 @@ private: std::unique_ptr _frameTimer { nullptr }; quint16 _outgoingSequenceNumber { 0 }; + // when the injector is local, we need this + AudioHRTF _localHRTF; friend class AudioInjectorManager; }; - + #endif // hifi_AudioInjector_h diff --git a/libraries/audio/src/AudioReverb.cpp b/libraries/audio/src/AudioReverb.cpp index 5c57e92ce5..c561231376 100644 --- a/libraries/audio/src/AudioReverb.cpp +++ b/libraries/audio/src/AudioReverb.cpp @@ -13,13 +13,13 @@ #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) @@ -1725,7 +1725,7 @@ void ReverbImpl::reset() { // Public API // -static const int REVERB_BLOCK = 1024; +static const int REVERB_BLOCK = 256; AudioReverb::AudioReverb(float sampleRate) { @@ -1804,7 +1804,7 @@ void AudioReverb::render(float** inputs, float** outputs, int numFrames) { #include // convert int16_t to float, deinterleave stereo -void AudioReverb::convertInputFromInt16(const int16_t* input, float** outputs, int numFrames) { +void AudioReverb::convertInput(const int16_t* input, float** outputs, int numFrames) { __m128 scale = _mm_set1_ps(1/32768.0f); int i = 0; @@ -1855,8 +1855,8 @@ static inline __m128 dither4() { 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) { +// convert float to int16_t with dither, interleave stereo +void AudioReverb::convertOutput(float** inputs, int16_t* output, int numFrames) { __m128 scale = _mm_set1_ps(32768.0f); int i = 0; @@ -1898,10 +1898,48 @@ void AudioReverb::convertOutputToInt16(float** inputs, int16_t* output, int numF } } +// deinterleave stereo +void AudioReverb::convertInput(const float* input, float** outputs, int numFrames) { + + int i = 0; + for (; i < numFrames - 3; i += 4) { + __m128 f0 = _mm_loadu_ps(&input[2*i + 0]); + __m128 f1 = _mm_loadu_ps(&input[2*i + 4]); + + // deinterleave + _mm_storeu_ps(&outputs[0][i], _mm_shuffle_ps(f0, f1, _MM_SHUFFLE(2,0,2,0))); + _mm_storeu_ps(&outputs[1][i], _mm_shuffle_ps(f0, f1, _MM_SHUFFLE(3,1,3,1))); + } + for (; i < numFrames; i++) { + // deinterleave + outputs[0][i] = input[2*i + 0]; + outputs[1][i] = input[2*i + 1]; + } +} + +// interleave stereo +void AudioReverb::convertOutput(float** inputs, float* output, int numFrames) { + + int i = 0; + for(; i < numFrames - 3; i += 4) { + __m128 f0 = _mm_loadu_ps(&inputs[0][i]); + __m128 f1 = _mm_loadu_ps(&inputs[1][i]); + + // interleave + _mm_storeu_ps(&output[2*i + 0],_mm_unpacklo_ps(f0,f1)); + _mm_storeu_ps(&output[2*i + 4],_mm_unpackhi_ps(f0,f1)); + } + for(; i < numFrames; i++) { + // interleave + output[2*i + 0] = inputs[0][i]; + output[2*i + 1] = inputs[1][i]; + } +} + #else // convert int16_t to float, deinterleave stereo -void AudioReverb::convertInputFromInt16(const int16_t* input, float** outputs, int numFrames) { +void AudioReverb::convertInput(const int16_t* input, float** outputs, int numFrames) { const float scale = 1/32768.0f; for (int i = 0; i < numFrames; i++) { @@ -1919,8 +1957,8 @@ static inline float dither() { 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) { +// convert float to int16_t with dither, interleave stereo +void AudioReverb::convertOutput(float** inputs, int16_t* output, int numFrames) { const float scale = 32768.0f; for (int i = 0; i < numFrames; i++) { @@ -1944,6 +1982,26 @@ void AudioReverb::convertOutputToInt16(float** inputs, int16_t* output, int numF } } +// deinterleave stereo +void AudioReverb::convertInput(const float* input, float** outputs, int numFrames) { + + for (int i = 0; i < numFrames; i++) { + // deinterleave + outputs[0][i] = input[2*i + 0]; + outputs[1][i] = input[2*i + 1]; + } +} + +// interleave stereo +void AudioReverb::convertOutput(float** inputs, float* output, int numFrames) { + + for (int i = 0; i < numFrames; i++) { + // interleave + output[2*i + 0] = inputs[0][i]; + output[2*i + 1] = inputs[1][i]; + } +} + #endif // @@ -1955,11 +2013,32 @@ void AudioReverb::render(const int16_t* input, int16_t* output, int numFrames) { int n = MIN(numFrames, REVERB_BLOCK); - convertInputFromInt16(input, _inout, n); + convertInput(input, _inout, n); _impl->process(_inout, _inout, n); - convertOutputToInt16(_inout, output, n); + convertOutput(_inout, output, n); + + input += 2 * n; + output += 2 * n; + numFrames -= n; + } +} + +// +// This version handles input/output as interleaved float +// +void AudioReverb::render(const float* input, float* output, int numFrames) { + + while (numFrames) { + + int n = MIN(numFrames, REVERB_BLOCK); + + convertInput(input, _inout, n); + + _impl->process(_inout, _inout, n); + + convertOutput(_inout, output, n); input += 2 * n; output += 2 * n; diff --git a/libraries/audio/src/AudioReverb.h b/libraries/audio/src/AudioReverb.h index 639d62d8ec..adb8890f6a 100644 --- a/libraries/audio/src/AudioReverb.h +++ b/libraries/audio/src/AudioReverb.h @@ -64,13 +64,20 @@ public: // interleaved int16_t input/output void render(const int16_t* input, int16_t* output, int numFrames); + // interleaved float input/output + void render(const float* input, float* 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); + + void convertInput(const int16_t* input, float** outputs, int numFrames); + void convertOutput(float** inputs, int16_t* output, int numFrames); + + void convertInput(const float* input, float** outputs, int numFrames); + void convertOutput(float** inputs, float* output, int numFrames); }; #endif // hifi_AudioReverb_h diff --git a/libraries/audio/src/AudioSRC.cpp b/libraries/audio/src/AudioSRC.cpp index c187d381a4..5dba63b349 100644 --- a/libraries/audio/src/AudioSRC.cpp +++ b/libraries/audio/src/AudioSRC.cpp @@ -14,552 +14,11 @@ #include #include "AudioSRC.h" +#include "AudioSRCData.h" -// -// prototype lowpass filter -// -// Minimum-phase equiripple FIR -// taps = 96, oversampling = 32 -// -// passband = 0.918 -// stopband = 1.010 -// passband ripple = +-0.01dB -// stopband attn = -125dB (-70dB at 1.000) -// -static const int PROTOTYPE_TAPS = 96; // filter taps per phase -static const int PROTOTYPE_PHASES = 32; // oversampling factor - -static const float prototypeFilter[PROTOTYPE_TAPS * PROTOTYPE_PHASES] = { - 0.00000000e+00f, 1.55021703e-05f, 1.46054865e-05f, 2.07057160e-05f, 2.91335519e-05f, 4.00091078e-05f, - 5.33544450e-05f, 7.03618468e-05f, 9.10821639e-05f, 1.16484613e-04f, 1.47165999e-04f, 1.84168304e-04f, - 2.28429617e-04f, 2.80913884e-04f, 3.42940399e-04f, 4.15773039e-04f, 5.01023255e-04f, 6.00234953e-04f, - 7.15133271e-04f, 8.47838855e-04f, 1.00032516e-03f, 1.17508881e-03f, 1.37452550e-03f, 1.60147614e-03f, - 1.85886458e-03f, 2.14985024e-03f, 2.47783071e-03f, 2.84666764e-03f, 3.26016878e-03f, 3.72252797e-03f, - 4.23825900e-03f, 4.81207874e-03f, 5.44904143e-03f, 6.15447208e-03f, 6.93399929e-03f, 7.79337059e-03f, - 8.73903392e-03f, 9.77729117e-03f, 1.09149561e-02f, 1.21591316e-02f, 1.35171164e-02f, 1.49965439e-02f, - 1.66053136e-02f, 1.83515384e-02f, 2.02435362e-02f, 2.22899141e-02f, 2.44995340e-02f, 2.68813362e-02f, - 2.94443254e-02f, 3.21979928e-02f, 3.51514690e-02f, 3.83143719e-02f, 4.16960560e-02f, 4.53060504e-02f, - 4.91538115e-02f, 5.32486197e-02f, 5.75998650e-02f, 6.22164253e-02f, 6.71072811e-02f, 7.22809789e-02f, - 7.77457552e-02f, 8.35095233e-02f, 8.95796944e-02f, 9.59631768e-02f, 1.02666457e-01f, 1.09695215e-01f, - 1.17054591e-01f, 1.24748885e-01f, 1.32781656e-01f, 1.41155521e-01f, 1.49872243e-01f, 1.58932534e-01f, - 1.68335961e-01f, 1.78081143e-01f, 1.88165339e-01f, 1.98584621e-01f, 2.09333789e-01f, 2.20406193e-01f, - 2.31793899e-01f, 2.43487398e-01f, 2.55475740e-01f, 2.67746404e-01f, 2.80285305e-01f, 2.93076743e-01f, - 3.06103423e-01f, 3.19346351e-01f, 3.32784916e-01f, 3.46396772e-01f, 3.60158039e-01f, 3.74043042e-01f, - 3.88024564e-01f, 4.02073759e-01f, 4.16160177e-01f, 4.30251886e-01f, 4.44315429e-01f, 4.58315954e-01f, - 4.72217175e-01f, 4.85981675e-01f, 4.99570709e-01f, 5.12944586e-01f, 5.26062401e-01f, 5.38882630e-01f, - 5.51362766e-01f, 5.63459860e-01f, 5.75130384e-01f, 5.86330458e-01f, 5.97016050e-01f, 6.07143161e-01f, - 6.16667840e-01f, 6.25546499e-01f, 6.33735979e-01f, 6.41193959e-01f, 6.47878856e-01f, 6.53750084e-01f, - 6.58768549e-01f, 6.62896349e-01f, 6.66097381e-01f, 6.68337353e-01f, 6.69583869e-01f, 6.69807061e-01f, - 6.68979117e-01f, 6.67075139e-01f, 6.64072812e-01f, 6.59952827e-01f, 6.54699116e-01f, 6.48298688e-01f, - 6.40742160e-01f, 6.32023668e-01f, 6.22141039e-01f, 6.11095903e-01f, 5.98893921e-01f, 5.85544600e-01f, - 5.71061707e-01f, 5.55463040e-01f, 5.38770639e-01f, 5.21010762e-01f, 5.02213839e-01f, 4.82414572e-01f, - 4.61651859e-01f, 4.39968628e-01f, 4.17412000e-01f, 3.94032951e-01f, 3.69886464e-01f, 3.45031084e-01f, - 3.19529091e-01f, 2.93446187e-01f, 2.66851164e-01f, 2.39815999e-01f, 2.12415399e-01f, 1.84726660e-01f, - 1.56829293e-01f, 1.28804933e-01f, 1.00736965e-01f, 7.27100355e-02f, 4.48100810e-02f, 1.71237415e-02f, - -1.02620228e-02f, -3.72599591e-02f, -6.37832871e-02f, -8.97457733e-02f, -1.15062201e-01f, -1.39648782e-01f, - -1.63423488e-01f, -1.86306368e-01f, -2.08220103e-01f, -2.29090072e-01f, -2.48845046e-01f, -2.67417270e-01f, - -2.84742946e-01f, -3.00762597e-01f, -3.15421127e-01f, -3.28668542e-01f, -3.40459849e-01f, -3.50755400e-01f, - -3.59521402e-01f, -3.66729768e-01f, -3.72358475e-01f, -3.76391839e-01f, -3.78820421e-01f, -3.79641287e-01f, - -3.78858203e-01f, -3.76481336e-01f, -3.72527677e-01f, -3.67020780e-01f, -3.59990760e-01f, -3.51474372e-01f, - -3.41514630e-01f, -3.30160971e-01f, -3.17468898e-01f, -3.03499788e-01f, -2.88320749e-01f, -2.72004315e-01f, - -2.54628056e-01f, -2.36274454e-01f, -2.17030464e-01f, -1.96986952e-01f, -1.76238733e-01f, -1.54883647e-01f, - -1.33022496e-01f, -1.10758449e-01f, -8.81964466e-02f, -6.54430504e-02f, -4.26055475e-02f, -1.97916415e-02f, - 2.89108184e-03f, 2.53355868e-02f, 4.74362201e-02f, 6.90887518e-02f, 9.01914308e-02f, 1.10644978e-01f, - 1.30353494e-01f, 1.49224772e-01f, 1.67170735e-01f, 1.84107975e-01f, 1.99958067e-01f, 2.14648181e-01f, - 2.28111323e-01f, 2.40286622e-01f, 2.51119890e-01f, 2.60563701e-01f, 2.68577740e-01f, 2.75129027e-01f, - 2.80192144e-01f, 2.83749177e-01f, 2.85790223e-01f, 2.86312986e-01f, 2.85323221e-01f, 2.82834421e-01f, - 2.78867915e-01f, 2.73452721e-01f, 2.66625431e-01f, 2.58429983e-01f, 2.48917457e-01f, 2.38145826e-01f, - 2.26179680e-01f, 2.13089734e-01f, 1.98952740e-01f, 1.83850758e-01f, 1.67870897e-01f, 1.51104879e-01f, - 1.33648388e-01f, 1.15600665e-01f, 9.70639763e-02f, 7.81429119e-02f, 5.89439889e-02f, 3.95749746e-02f, - 2.01442353e-02f, 7.60241152e-04f, -1.84690990e-02f, -3.74370397e-02f, -5.60385970e-02f, -7.41711039e-02f, - -9.17348686e-02f, -1.08633632e-01f, -1.24775254e-01f, -1.40071993e-01f, -1.54441372e-01f, -1.67806284e-01f, - -1.80095654e-01f, -1.91244732e-01f, -2.01195605e-01f, -2.09897310e-01f, -2.17306320e-01f, -2.23386736e-01f, - -2.28110407e-01f, -2.31457193e-01f, -2.33415044e-01f, -2.33980051e-01f, -2.33156463e-01f, -2.30956673e-01f, - -2.27401097e-01f, -2.22518148e-01f, -2.16343899e-01f, -2.08921985e-01f, -2.00303365e-01f, -1.90545790e-01f, - -1.79713804e-01f, -1.67877977e-01f, -1.55114789e-01f, -1.41505907e-01f, -1.27137921e-01f, -1.12101628e-01f, - -9.64915640e-02f, -8.04054232e-02f, -6.39434707e-02f, -4.72078814e-02f, -3.03021635e-02f, -1.33305082e-02f, - 3.60284977e-03f, 2.03942507e-02f, 3.69413014e-02f, 5.31433810e-02f, 6.89024656e-02f, 8.41234679e-02f, - 9.87150268e-02f, 1.12589969e-01f, 1.25665865e-01f, 1.37865538e-01f, 1.49117506e-01f, 1.59356490e-01f, - 1.68523664e-01f, 1.76567229e-01f, 1.83442499e-01f, 1.89112308e-01f, 1.93547212e-01f, 1.96725586e-01f, - 1.98633878e-01f, 1.99266486e-01f, 1.98625999e-01f, 1.96723008e-01f, 1.93576075e-01f, 1.89211557e-01f, - 1.83663562e-01f, 1.76973516e-01f, 1.69190033e-01f, 1.60368490e-01f, 1.50570805e-01f, 1.39864815e-01f, - 1.28324021e-01f, 1.16026978e-01f, 1.03056879e-01f, 8.95008829e-02f, 7.54496798e-02f, 6.09968238e-02f, - 4.62380664e-02f, 3.12708901e-02f, 1.61936956e-02f, 1.10531988e-03f, -1.38957653e-02f, -2.87119784e-02f, - -4.32472742e-02f, -5.74078385e-02f, -7.11026311e-02f, -8.42439713e-02f, -9.67481917e-02f, -1.08536049e-01f, - -1.19533350e-01f, -1.29671345e-01f, -1.38887238e-01f, -1.47124498e-01f, -1.54333373e-01f, -1.60470968e-01f, - -1.65501755e-01f, -1.69397631e-01f, -1.72138140e-01f, -1.73710602e-01f, -1.74110159e-01f, -1.73339798e-01f, - -1.71410274e-01f, -1.68340111e-01f, -1.64155335e-01f, -1.58889414e-01f, -1.52582850e-01f, -1.45283122e-01f, - -1.37044042e-01f, -1.27925722e-01f, -1.17993860e-01f, -1.07319421e-01f, -9.59781808e-02f, -8.40500777e-02f, - -7.16188049e-02f, -5.87710561e-02f, -4.55961475e-02f, -3.21851919e-02f, -1.86306406e-02f, -5.02554942e-03f, - 8.53698384e-03f, 2.19645467e-02f, 3.51659468e-02f, 4.80518693e-02f, 6.05355056e-02f, 7.25330700e-02f, - 8.39645094e-02f, 9.47537898e-02f, 1.04829753e-01f, 1.14126254e-01f, 1.22582788e-01f, 1.30144907e-01f, - 1.36764459e-01f, 1.42400029e-01f, 1.47017076e-01f, 1.50588312e-01f, 1.53093700e-01f, 1.54520736e-01f, - 1.54864367e-01f, 1.54127119e-01f, 1.52318991e-01f, 1.49457408e-01f, 1.45567062e-01f, 1.40679709e-01f, - 1.34833933e-01f, 1.28074855e-01f, 1.20453893e-01f, 1.12028129e-01f, 1.02860307e-01f, 9.30178765e-02f, - 8.25730032e-02f, 7.16016450e-02f, 6.01833134e-02f, 4.84002546e-02f, 3.63370724e-02f, 2.40800037e-02f, - 1.17163168e-02f, -6.66217400e-04f, -1.29801121e-02f, -2.51385315e-02f, -3.70562030e-02f, -4.86497748e-02f, - -5.98384928e-02f, -7.05447859e-02f, -8.06947592e-02f, -9.02187441e-02f, -9.90517313e-02f, -1.07133911e-01f, - -1.14410951e-01f, -1.20834483e-01f, -1.26362422e-01f, -1.30959116e-01f, -1.34595787e-01f, -1.37250547e-01f, - -1.38908600e-01f, -1.39562374e-01f, -1.39211442e-01f, -1.37862602e-01f, -1.35529795e-01f, -1.32233909e-01f, - -1.28002721e-01f, -1.22870611e-01f, -1.16878278e-01f, -1.10072477e-01f, -1.02505698e-01f, -9.42356124e-02f, - -8.53248753e-02f, -7.58404912e-02f, -6.58532924e-02f, -5.54376360e-02f, -4.46705953e-02f, -3.36315414e-02f, - -2.24015972e-02f, -1.10628991e-02f, 3.01894735e-04f, 1.16101918e-02f, 2.27801642e-02f, 3.37311642e-02f, - 4.43845430e-02f, 5.46640016e-02f, 6.44962637e-02f, 7.38115400e-02f, 8.25440784e-02f, 9.06325572e-02f, - 9.80206066e-02f, 1.04657146e-01f, 1.10496723e-01f, 1.15499920e-01f, 1.19633523e-01f, 1.22870824e-01f, - 1.25191729e-01f, 1.26582959e-01f, 1.27038061e-01f, 1.26557494e-01f, 1.25148528e-01f, 1.22825305e-01f, - 1.19608512e-01f, 1.15525479e-01f, 1.10609643e-01f, 1.04900592e-01f, 9.84435537e-02f, 9.12890948e-02f, - 8.34927732e-02f, 7.51146973e-02f, 6.62190194e-02f, 5.68735547e-02f, 4.71491262e-02f, 3.71191855e-02f, - 2.68591932e-02f, 1.64459573e-02f, 5.95731808e-03f, -4.52874940e-03f, -1.49344723e-02f, -2.51829130e-02f, - -3.51986373e-02f, -4.49081427e-02f, -5.42404654e-02f, -6.31276969e-02f, -7.15054163e-02f, -7.93132713e-02f, - -8.64953327e-02f, -9.30005042e-02f, -9.87829011e-02f, -1.03802223e-01f, -1.08023943e-01f, -1.11419636e-01f, - -1.13967111e-01f, -1.15650603e-01f, -1.16460855e-01f, -1.16395152e-01f, -1.15457368e-01f, -1.13657871e-01f, - -1.11013433e-01f, -1.07547117e-01f, -1.03288073e-01f, -9.82712708e-02f, -9.25372646e-02f, -8.61318657e-02f, - -7.91057486e-02f, -7.15141053e-02f, -6.34161588e-02f, -5.48747791e-02f, -4.59559696e-02f, -3.67282941e-02f, - -2.72624874e-02f, -1.76307914e-02f, -7.90648674e-03f, 1.83670340e-03f, 1.15251424e-02f, 2.10858716e-02f, - 3.04471304e-02f, 3.95388944e-02f, 4.82933904e-02f, 5.66456655e-02f, 6.45340054e-02f, 7.19003487e-02f, - 7.86908695e-02f, 8.48562395e-02f, 9.03519908e-02f, 9.51389501e-02f, 9.91834077e-02f, 1.02457361e-01f, - 1.04938834e-01f, 1.06611872e-01f, 1.07466724e-01f, 1.07499917e-01f, 1.06714213e-01f, 1.05118588e-01f, - 1.02728167e-01f, 9.95640680e-02f, 9.56532488e-02f, 9.10282406e-02f, 8.57269309e-02f, 7.97922261e-02f, - 7.32717395e-02f, 6.62174249e-02f, 5.86850536e-02f, 5.07339959e-02f, 4.24265058e-02f, 3.38274345e-02f, - 2.50036502e-02f, 1.60234844e-02f, 6.95628026e-03f, -2.12820655e-03f, -1.11602438e-02f, -2.00708281e-02f, - -2.87920337e-02f, -3.72576320e-02f, -4.54035426e-02f, -5.31684173e-02f, -6.04938939e-02f, -6.73253212e-02f, - -7.36119310e-02f, -7.93072981e-02f, -8.43697556e-02f, -8.87625537e-02f, -9.24542939e-02f, -9.54189981e-02f, - -9.76364402e-02f, -9.90921435e-02f, -9.97776003e-02f, -9.96902366e-02f, -9.88334463e-02f, -9.72165780e-02f, - -9.48547668e-02f, -9.17688999e-02f, -8.79853312e-02f, -8.35357688e-02f, -7.84569594e-02f, -7.27903677e-02f, - -6.65818940e-02f, -5.98814932e-02f, -5.27427333e-02f, -4.52224733e-02f, -3.73802459e-02f, -2.92780037e-02f, - -2.09794209e-02f, -1.25495498e-02f, -4.05425988e-03f, 4.44034349e-03f, 1.28682571e-02f, 2.11643361e-02f, - 2.92645357e-02f, 3.71066200e-02f, 4.46305203e-02f, 5.17788267e-02f, 5.84972389e-02f, 6.47349496e-02f, - 7.04450836e-02f, 7.55849928e-02f, 8.01165748e-02f, 8.40066506e-02f, 8.72270848e-02f, 8.97550618e-02f, - 9.15732179e-02f, 9.26698315e-02f, 9.30387881e-02f, 9.26796720e-02f, 9.15978025e-02f, 8.98040443e-02f, - 8.73148489e-02f, 8.41520461e-02f, 8.03426093e-02f, 7.59185468e-02f, 7.09165136e-02f, 6.53776255e-02f, - 5.93470480e-02f, 5.28736293e-02f, 4.60095655e-02f, 3.88099545e-02f, 3.13323302e-02f, 2.36362162e-02f, - 1.57827398e-02f, 7.83395091e-03f, -1.47413782e-04f, -8.09864153e-03f, -1.59574406e-02f, -2.36623595e-02f, - -3.11534717e-02f, -3.83725840e-02f, -4.52638947e-02f, -5.17743411e-02f, -5.78539729e-02f, -6.34564348e-02f, - -6.85392092e-02f, -7.30640654e-02f, -7.69971954e-02f, -8.03096220e-02f, -8.29772975e-02f, -8.49813524e-02f, - -8.63081836e-02f, -8.69495746e-02f, -8.69027157e-02f, -8.61702687e-02f, -8.47602668e-02f, -8.26860569e-02f, - -7.99661981e-02f, -7.66242997e-02f, -7.26887788e-02f, -6.81926752e-02f, -6.31733712e-02f, -5.76722279e-02f, - -5.17343061e-02f, -4.54080069e-02f, -3.87446321e-02f, -3.17980032e-02f, -2.46239897e-02f, -1.72801497e-02f, - -9.82518156e-03f, -2.31845300e-03f, 5.18037510e-03f, 1.26119044e-02f, 1.99174857e-02f, 2.70395921e-02f, - 3.39223499e-02f, 4.05119404e-02f, 4.67570465e-02f, 5.26092142e-02f, 5.80232695e-02f, 6.29576539e-02f, - 6.73747113e-02f, 7.12410320e-02f, 7.45276905e-02f, 7.72104218e-02f, 7.92698394e-02f, 8.06915952e-02f, - 8.14664004e-02f, 8.15901977e-02f, 8.10640907e-02f, 7.98943315e-02f, 7.80922975e-02f, 7.56743792e-02f, - 7.26617861e-02f, 6.90804346e-02f, 6.49606433e-02f, 6.03370049e-02f, 5.52479503e-02f, 4.97355660e-02f, - 4.38451300e-02f, 3.76248662e-02f, 3.11254263e-02f, 2.43995757e-02f, 1.75017105e-02f, 1.04874823e-02f, - 3.41321948e-03f, -3.66433362e-03f, -1.06886566e-02f, -1.76037566e-02f, -2.43547422e-02f, -3.08881238e-02f, - -3.71523818e-02f, -4.30982377e-02f, -4.86791529e-02f, -5.38515978e-02f, -5.85754991e-02f, -6.28144137e-02f, - -6.65359631e-02f, -6.97119559e-02f, -7.23186409e-02f, -7.43369897e-02f, -7.57526047e-02f, -7.65560812e-02f, - -7.67428560e-02f, -7.63134051e-02f, -7.52730583e-02f, -7.36321241e-02f, -7.14055927e-02f, -6.86132027e-02f, - -6.52791213e-02f, -6.14318004e-02f, -5.71037475e-02f, -5.23312158e-02f, -4.71539306e-02f, -4.16147519e-02f, - -3.57593331e-02f, -2.96357023e-02f, -2.32939478e-02f, -1.67857228e-02f, -1.01639251e-02f, -3.48213128e-03f, - 3.20566951e-03f, 9.84566549e-03f, 1.63845318e-02f, 2.27699627e-02f, 2.89509937e-02f, 3.48784838e-02f, - 4.05054571e-02f, 4.57875191e-02f, 5.06831561e-02f, 5.51541055e-02f, 5.91656321e-02f, 6.26867948e-02f, - 6.56907214e-02f, 6.81547545e-02f, 7.00607045e-02f, 7.13948753e-02f, 7.21482790e-02f, 7.23165894e-02f, - 7.19002973e-02f, 7.09044846e-02f, 6.93390331e-02f, 6.72183039e-02f, 6.45611568e-02f, 6.13906537e-02f, - 5.77340810e-02f, 5.36223917e-02f, 4.90902973e-02f, 4.41756853e-02f, 3.89195025e-02f, 3.33653266e-02f, - 2.75589553e-02f, 2.15482187e-02f, 1.53823433e-02f, 9.11173206e-03f, 2.78750380e-03f, -3.53899736e-03f, - -9.81648845e-03f, -1.59942887e-02f, -2.20226002e-02f, -2.78530676e-02f, -3.34389835e-02f, -3.87358558e-02f, - -4.37015752e-02f, -4.82968641e-02f, -5.24856104e-02f, -5.62350079e-02f, -5.95160314e-02f, -6.23034090e-02f, - -6.45760369e-02f, -6.63170246e-02f, -6.75138263e-02f, -6.81583864e-02f, -6.82471093e-02f, -6.77809819e-02f, - -6.67654439e-02f, -6.52104027e-02f, -6.31301405e-02f, -6.05431381e-02f, -5.74719510e-02f, -5.39430121e-02f, - -4.99864152e-02f, -4.56356108e-02f, -4.09271785e-02f, -3.59005358e-02f, -3.05975021e-02f, -2.50620982e-02f, - -1.93400931e-02f, -1.34786109e-02f, -7.52582921e-03f, -1.53047296e-03f, 4.45846396e-03f, 1.03922252e-02f, - 1.62226043e-02f, 2.19024111e-02f, 2.73857927e-02f, 3.26286453e-02f, 3.75889120e-02f, 4.22270162e-02f, - 4.65060678e-02f, 5.03922602e-02f, 5.38550360e-02f, 5.68673912e-02f, 5.94061299e-02f, 6.14518959e-02f, - 6.29894927e-02f, 6.40078422e-02f, 6.45002081e-02f, 6.44641312e-02f, 6.39014463e-02f, 6.28183549e-02f, - 6.12252434e-02f, 5.91366226e-02f, 5.65710713e-02f, 5.35509478e-02f, 5.01023211e-02f, 4.62546289e-02f, - 4.20405644e-02f, 3.74956324e-02f, 3.26580309e-02f, 2.75681921e-02f, 2.22685138e-02f, 1.68029869e-02f, - 1.12168479e-02f, 5.55616360e-03f, -1.32475496e-04f, -5.80242145e-03f, -1.14072870e-02f, -1.69013632e-02f, - -2.22399629e-02f, -2.73798231e-02f, -3.22793559e-02f, -3.68992177e-02f, -4.12022700e-02f, -4.51542301e-02f, - -4.87237130e-02f, -5.18825743e-02f, -5.46061242e-02f, -5.68733215e-02f, -5.86668721e-02f, -5.99735198e-02f, - -6.07838952e-02f, -6.10928895e-02f, -6.08993923e-02f, -6.02064781e-02f, -5.90213291e-02f, -5.73550887e-02f, - -5.52228853e-02f, -5.26435817e-02f, -4.96396897e-02f, -4.62371294e-02f, -4.24650256e-02f, -3.83554628e-02f, - -3.39432096e-02f, -2.92654225e-02f, -2.43613233e-02f, -1.92718970e-02f, -1.40395616e-02f, -8.70771728e-03f, - -3.32056777e-03f, 2.07744785e-03f, 7.44190391e-03f, 1.27287222e-02f, 1.78946228e-02f, 2.28975002e-02f, - 2.76965843e-02f, 3.22530140e-02f, 3.65299534e-02f, 4.04930363e-02f, 4.41105069e-02f, 4.73536159e-02f, - 5.01967201e-02f, 5.26175750e-02f, 5.45974724e-02f, 5.61213729e-02f, 5.71780843e-02f, 5.77601946e-02f, - 5.78643759e-02f, 5.74910914e-02f, 5.66448597e-02f, 5.53340158e-02f, 5.35707338e-02f, 5.13708843e-02f, - 4.87538683e-02f, 4.57425137e-02f, 4.23627999e-02f, 3.86437075e-02f, 3.46169024e-02f, 3.03165387e-02f, - 2.57788894e-02f, 2.10421222e-02f, 1.61459251e-02f, 1.11311994e-02f, 6.03970466e-03f, 9.13695817e-04f, - -4.20433431e-03f, -9.27218149e-03f, -1.42480682e-02f, -1.90911878e-02f, -2.37618648e-02f, -2.82220093e-02f, - -3.24353766e-02f, -3.63678336e-02f, -3.99876924e-02f, -4.32659237e-02f, -4.61764207e-02f, -4.86961602e-02f, - -5.08054551e-02f, -5.24880386e-02f, -5.37312181e-02f, -5.45260166e-02f, -5.48671104e-02f, -5.47530531e-02f, - -5.41860463e-02f, -5.31721475e-02f, -5.17210363e-02f, -4.98459868e-02f, -4.75637647e-02f, -4.48944406e-02f, - -4.18612746e-02f, -3.84904206e-02f, -3.48107925e-02f, -3.08537797e-02f, -2.66529685e-02f, -2.22438695e-02f, - -1.76636682e-02f, -1.29507560e-02f, -8.14466071e-03f, -3.28544776e-03f, 1.58643018e-03f, 6.43050440e-03f, - 1.12067405e-02f, 1.58756642e-02f, 2.03989020e-02f, 2.47393345e-02f, 2.88614617e-02f, 3.27317634e-02f, - 3.63187992e-02f, 3.95936470e-02f, 4.25300387e-02f, 4.51045672e-02f, 4.72968940e-02f, 4.90899703e-02f, - 5.04700047e-02f, 5.14267809e-02f, 5.19535643e-02f, 5.20472034e-02f, 5.17082287e-02f, 5.09406434e-02f, - 4.97521048e-02f, 4.81537188e-02f, 4.61599131e-02f, 4.37884262e-02f, 4.10600706e-02f, 3.79985488e-02f, - 3.46302622e-02f, 3.09841217e-02f, 2.70912412e-02f, 2.29847199e-02f, 1.86992847e-02f, 1.42711599e-02f, - 9.73752669e-03f, 5.13643650e-03f, 5.06379454e-04f, -4.11408166e-03f, -8.68649476e-03f, -1.31729621e-02f, - -1.75363807e-02f, -2.17408089e-02f, -2.57516979e-02f, -2.95362143e-02f, -3.30635093e-02f, -3.63049622e-02f, - -3.92344048e-02f, -4.18283298e-02f, -4.40661418e-02f, -4.59301913e-02f, -4.74060505e-02f, -4.84825511e-02f, - -4.91518827e-02f, -4.94096235e-02f, -4.92548579e-02f, -4.86900251e-02f, -4.77210458e-02f, -4.63571741e-02f, - -4.46108878e-02f, -4.24979107e-02f, -4.00368564e-02f, -3.72492987e-02f, -3.41594108e-02f, -3.07938448e-02f, - -2.71814552e-02f, -2.33531198e-02f, -1.93413598e-02f, -1.51802063e-02f, -1.09048013e-02f, -6.55114338e-03f, - -2.15581014e-03f, 2.24443555e-03f, 6.61280814e-03f, 1.09129453e-02f, 1.51091980e-02f, 1.91667630e-02f, - 2.30522168e-02f, 2.67335907e-02f, 3.01807365e-02f, 3.33655579e-02f, 3.62622051e-02f, 3.88473226e-02f, - 4.11002204e-02f, 4.30030300e-02f, 4.45408790e-02f, 4.57019705e-02f, 4.64777109e-02f, 4.68627135e-02f, - 4.68549093e-02f, 4.64554958e-02f, 4.56689373e-02f, 4.45029599e-02f, 4.29683919e-02f, 4.10791386e-02f, - 3.88520159e-02f, 3.63066475e-02f, 3.34652385e-02f, 3.03523892e-02f, 2.69949681e-02f, 2.34217263e-02f, - 1.96632025e-02f, 1.57513974e-02f, 1.17194459e-02f, 7.60145677e-03f, 3.43215481e-03f, -7.53454950e-04f, - -4.92025229e-03f, -9.03345904e-03f, -1.30587503e-02f, -1.69627406e-02f, -2.07130441e-02f, -2.42787472e-02f, - -2.76304969e-02f, -3.07408842e-02f, -3.35845310e-02f, -3.61384026e-02f, -3.83819804e-02f, -4.02973364e-02f, - -4.18693911e-02f, -4.30859849e-02f, -4.39379525e-02f, -4.44192202e-02f, -4.45268207e-02f, -4.42609489e-02f, - -4.36249417e-02f, -4.26251693e-02f, -4.12710965e-02f, -3.95751119e-02f, -3.75524034e-02f, -3.52209020e-02f, - -3.26010732e-02f, -2.97156826e-02f, -2.65897306e-02f, -2.32501339e-02f, -1.97255230e-02f, -1.60459906e-02f, - -1.22428645e-02f, -8.34840613e-03f, -4.39555788e-03f, -4.17641093e-04f, 3.55186529e-03f, 7.47969548e-03f, - 1.13330289e-02f, 1.50796895e-02f, 1.86886063e-02f, 2.21298440e-02f, 2.53750227e-02f, 2.83974776e-02f, - 3.11724713e-02f, 3.36774564e-02f, 3.58921485e-02f, 3.77988281e-02f, 3.93823848e-02f, 4.06304645e-02f, - 4.15335460e-02f, 4.20850895e-02f, 4.22814530e-02f, 4.21220657e-02f, 4.16092724e-02f, 4.07484568e-02f, - 3.95478256e-02f, 3.80185099e-02f, 3.61742882e-02f, 3.40316228e-02f, 3.16093467e-02f, 2.89286854e-02f, - 2.60129143e-02f, 2.28872072e-02f, 1.95785162e-02f, 1.61151429e-02f, 1.25266872e-02f, 8.84367289e-03f, - 5.09737541e-03f, 1.31946573e-03f, -2.45819207e-03f, -6.20382907e-03f, -9.88599514e-03f, -1.34739714e-02f, - -1.69377975e-02f, -2.02487225e-02f, -2.33793144e-02f, -2.63038233e-02f, -2.89981802e-02f, -3.14404213e-02f, - -3.36107546e-02f, -3.54916723e-02f, -3.70682427e-02f, -3.83280672e-02f, -3.92614736e-02f, -3.98615776e-02f, - -4.01243243e-02f, -4.00484517e-02f, -3.96356708e-02f, -3.88903731e-02f, -3.78198781e-02f, -3.64341365e-02f, - -3.47457457e-02f, -3.27698392e-02f, -3.05238882e-02f, -2.80276282e-02f, -2.53028218e-02f, -2.23730957e-02f, - -1.92637467e-02f, -1.60015029e-02f, -1.26142882e-02f, -9.13104283e-03f, -5.58138981e-03f, -1.99542434e-03f, - 1.59649307e-03f, 5.16408174e-03f, 8.67737144e-03f, 1.21068581e-02f, 1.54239205e-02f, 1.86009100e-02f, - 2.16114772e-02f, 2.44306994e-02f, 2.70354163e-02f, 2.94042665e-02f, 3.15179985e-02f, 3.33595356e-02f, - 3.49141593e-02f, 3.61696229e-02f, 3.71161871e-02f, 3.77468512e-02f, 3.80571878e-02f, 3.80455485e-02f, - 3.77129900e-02f, 3.70632810e-02f, 3.61028508e-02f, 3.48407199e-02f, 3.32884428e-02f, 3.14600053e-02f, - 2.93716228e-02f, 2.70417408e-02f, 2.44907277e-02f, 2.17407576e-02f, 1.88156734e-02f, 1.57406803e-02f, - 1.25421761e-02f, 9.24754692e-03f, 5.88488640e-03f, 2.48280587e-03f, -9.29864758e-04f, -4.32426314e-03f, - -7.67179184e-03f, -1.09442952e-02f, -1.41143886e-02f, -1.71555974e-02f, -2.00425787e-02f, -2.27514891e-02f, - -2.52599054e-02f, -2.75472706e-02f, -2.95949315e-02f, -3.13863062e-02f, -3.29069832e-02f, -3.41450096e-02f, - -3.50907101e-02f, -3.57369992e-02f, -3.60793163e-02f, -3.61156751e-02f, -3.58467080e-02f, -3.52755740e-02f, - -3.44080617e-02f, -3.32523628e-02f, -3.18191314e-02f, -3.01213186e-02f, -2.81740846e-02f, -2.59946393e-02f, - -2.36021125e-02f, -2.10173975e-02f, -1.82629132e-02f, -1.53624700e-02f, -1.23410560e-02f, -9.22456599e-03f, - -6.03967755e-03f, -2.81350877e-03f, 4.26514319e-04f, 3.65292660e-03f, 6.83848944e-03f, 9.95638508e-03f, - 1.29804234e-02f, 1.58853076e-02f, 1.86468203e-02f, 2.12420277e-02f, 2.36494909e-02f, 2.58493792e-02f, - 2.78237450e-02f, 2.95565060e-02f, 3.10338053e-02f, 3.22438572e-02f, 3.31772716e-02f, 3.38269627e-02f, - 3.41883176e-02f, 3.42591610e-02f, 3.40397435e-02f, 3.35328606e-02f, 3.27436351e-02f, 3.16796573e-02f, - 3.03507246e-02f, 2.87689689e-02f, 2.69484839e-02f, 2.49054827e-02f, 2.26579086e-02f, 2.02254442e-02f, - 1.76292617e-02f, 1.48918382e-02f, 1.20368159e-02f, 9.08872468e-03f, 6.07283273e-03f, 3.01489838e-03f, - -5.90212194e-05f, -3.12287666e-03f, -6.15069532e-03f, -9.11695091e-03f, -1.19967033e-02f, -1.47657868e-02f, - -1.74011004e-02f, -1.98807214e-02f, -2.21841025e-02f, -2.42922632e-02f, -2.61879368e-02f, -2.78557311e-02f, - -2.92821801e-02f, -3.04559562e-02f, -3.13678907e-02f, -3.20110632e-02f, -3.23808087e-02f, -3.24749193e-02f, - -3.22933847e-02f, -3.18386269e-02f, -3.11153366e-02f, -3.01304804e-02f, -2.88932552e-02f, -2.74148734e-02f, - -2.57086673e-02f, -2.37898314e-02f, -2.16752343e-02f, -1.93835013e-02f, -1.69345799e-02f, -1.43497284e-02f, - -1.16513243e-02f, -8.86259097e-03f, -6.00748525e-03f, -3.11044903e-03f, -1.96143386e-04f, 2.71056658e-03f, - 5.58512222e-03f, 8.40318833e-03f, 1.11410160e-02f, 1.37756382e-02f, 1.62850338e-02f, 1.86482666e-02f, - 2.08457445e-02f, 2.28593437e-02f, 2.46725329e-02f, 2.62705694e-02f, 2.76405329e-02f, 2.87715470e-02f, - 2.96547092e-02f, 3.02833419e-02f, 3.06529059e-02f, 3.07610441e-02f, 3.06076742e-02f, 3.01949567e-02f, - 2.95271502e-02f, 2.86107876e-02f, 2.74543883e-02f, 2.60685701e-02f, 2.44657863e-02f, 2.26603655e-02f, - 2.06682557e-02f, 1.85070033e-02f, 1.61954603e-02f, 1.37537720e-02f, 1.12030588e-02f, 8.56537064e-03f, - 5.86336215e-03f, 3.12021752e-03f, 3.59345288e-04f, -2.39571357e-03f, -5.12158252e-03f, -7.79518527e-03f, - -1.03939536e-02f, -1.28961026e-02f, -1.52805838e-02f, -1.75275761e-02f, -1.96183935e-02f, -2.15357712e-02f, - -2.32639542e-02f, -2.47888545e-02f, -2.60981899e-02f, -2.71814567e-02f, -2.80302370e-02f, -2.86380088e-02f, - -2.90003996e-02f, -2.91151172e-02f, -2.89819544e-02f, -2.86028697e-02f, -2.79818317e-02f, -2.71249297e-02f, - -2.60401957e-02f, -2.47375751e-02f, -2.32288414e-02f, -2.15275091e-02f, -1.96486443e-02f, -1.76087964e-02f, - -1.54258426e-02f, -1.31187994e-02f, -1.07076937e-02f, -8.21335282e-03f, -5.65730582e-03f, -3.06143405e-03f, - -4.47990175e-04f, 2.16074548e-03f, 4.74260737e-03f, 7.27569124e-03f, 9.73864733e-03f, 1.21106824e-02f, - 1.43719841e-02f, 1.65036001e-02f, 1.84878471e-02f, 2.03083286e-02f, 2.19500531e-02f, 2.33996493e-02f, - 2.46453861e-02f, 2.56773512e-02f, 2.64874345e-02f, 2.70694463e-02f, 2.74192279e-02f, 2.75344951e-02f, - 2.74150667e-02f, 2.70627089e-02f, 2.64811913e-02f, 2.56761950e-02f, 2.46553112e-02f, 2.34279326e-02f, - 2.20051823e-02f, 2.03998041e-02f, 1.86260730e-02f, 1.66996483e-02f, 1.46373888e-02f, 1.24573628e-02f, - 1.01784699e-02f, 7.82046099e-03f, 5.40366356e-03f, 2.94886537e-03f, 4.77074685e-04f, -1.99056008e-03f, - -4.43309957e-03f, -6.82975366e-03f, -9.16032780e-03f, -1.14051392e-02f, -1.35453571e-02f, -1.55631186e-02f, - -1.74416221e-02f, -1.91653203e-02f, -2.07200521e-02f, -2.20931290e-02f, -2.32734389e-02f, -2.42515770e-02f, - -2.50198790e-02f, -2.55724740e-02f, -2.59053977e-02f, -2.60165073e-02f, -2.59056121e-02f, -2.55744100e-02f, - -2.50263861e-02f, -2.42670139e-02f, -2.33034172e-02f, -2.21444752e-02f, -2.08007704e-02f, -1.92843016e-02f, - -1.76086143e-02f, -1.57885066e-02f, -1.38399632e-02f, -1.17800468e-02f, -9.62665505e-03f, -7.39846180e-03f, - -5.11473979e-03f, -2.79509520e-03f, -4.59475153e-04f, 1.87219411e-03f, 4.18004886e-03f, 6.44446028e-03f, - 8.64630036e-03f, 1.07670050e-02f, 1.27887263e-02f, 1.46946183e-02f, 1.64687696e-02f, 1.80965074e-02f, - 1.95644657e-02f, 2.08606409e-02f, 2.19745569e-02f, 2.28973400e-02f, 2.36217678e-02f, 2.41423032e-02f, - 2.44552329e-02f, 2.45585559e-02f, 2.44521268e-02f, 2.41375247e-02f, 2.36181843e-02f, 2.28991883e-02f, - 2.19873596e-02f, 2.08911372e-02f, 1.96204854e-02f, 1.81868423e-02f, 1.66029686e-02f, 1.48829260e-02f, - 1.30418196e-02f, 1.10957823e-02f, 9.06176569e-03f, 6.95742371e-03f, 4.80095797e-03f, 2.61094572e-03f, - 4.06163422e-04f, -1.79448120e-03f, -3.97227507e-03f, -6.10867089e-03f, -8.18559133e-03f, -1.01855447e-02f, - -1.20916775e-02f, -1.38880736e-02f, -1.55597947e-02f, -1.70929424e-02f, -1.84749792e-02f, -1.96945768e-02f, - -2.07419008e-02f, -2.16086011e-02f, -2.22879060e-02f, -2.27746496e-02f, -2.30653527e-02f, -2.31582122e-02f, - -2.30530853e-02f, -2.27516002e-02f, -2.22569518e-02f, -2.15740851e-02f, -2.07094459e-02f, -1.96710504e-02f, - -1.84683607e-02f, -1.71122258e-02f, -1.56147530e-02f, -1.39891960e-02f, -1.22499260e-02f, -1.04121226e-02f, - -8.49187069e-03f, -6.50583812e-03f, -4.47121574e-03f, -2.40553061e-03f, -3.26560349e-04f, 1.74792849e-03f, - 3.80020986e-03f, 5.81284812e-03f, 7.76878436e-03f, 9.65152189e-03f, 1.14452321e-02f, 1.31348903e-02f, - 1.47064602e-02f, 1.61469015e-02f, 1.74443880e-02f, 1.85883329e-02f, 1.95694960e-02f, 2.03800747e-02f, - 2.10137416e-02f, 2.14657028e-02f, 2.17327470e-02f, 2.18132189e-02f, 2.17071096e-02f, 2.14159688e-02f, - 2.09429396e-02f, 2.02927056e-02f, 1.94714591e-02f, 1.84867806e-02f, 1.73476996e-02f, 1.60644888e-02f, - 1.46486021e-02f, 1.31126305e-02f, 1.14700918e-02f, 9.73543186e-03f, 7.92379251e-03f, 6.05090462e-03f, - 4.13301608e-03f, 2.18669055e-03f, 2.28581333e-04f, -1.72441072e-03f, -3.65572200e-03f, -5.54887990e-03f, - -7.38782061e-03f, -9.15706782e-03f, -1.08417082e-02f, -1.24276657e-02f, -1.39017311e-02f, -1.52516970e-02f, - -1.64664949e-02f, -1.75361817e-02f, -1.84521823e-02f, -1.92071599e-02f, -1.97953056e-02f, -2.02121243e-02f, - -2.04547147e-02f, -2.05216098e-02f, -2.04128534e-02f, -2.01300439e-02f, -1.96761990e-02f, -1.90558123e-02f, - -1.82748056e-02f, -1.73404276e-02f, -1.62612067e-02f, -1.50469098e-02f, -1.37084115e-02f, -1.22575769e-02f, - -1.07072432e-02f, -9.07102930e-03f, -7.36320826e-03f, -5.59869147e-03f, -3.79270806e-03f, -1.96092013e-03f, - -1.19027325e-04f, 1.71713152e-03f, 3.53191747e-03f, 5.30986343e-03f, 7.03590331e-03f, 8.69547560e-03f, - 1.02746006e-02f, 1.17601122e-02f, 1.31396009e-02f, 1.44016653e-02f, 1.55359973e-02f, 1.65332483e-02f, - 1.73855033e-02f, 1.80859434e-02f, 1.86291305e-02f, 1.90110277e-02f, 1.92289384e-02f, 1.92815880e-02f, - 1.91691688e-02f, 1.88932135e-02f, 1.84567183e-02f, 1.78639790e-02f, 1.71206377e-02f, 1.62336473e-02f, - 1.52110920e-02f, 1.40622274e-02f, 1.27973510e-02f, 1.14277163e-02f, 9.96541843e-03f, 8.42333112e-03f, - 6.81491991e-03f, 5.15420944e-03f, 3.45559138e-03f, 1.73374462e-03f, 3.49154958e-06f, -1.72033182e-03f, - -3.42300908e-03f, -5.09002877e-03f, -6.70728983e-03f, -8.26110592e-03f, -9.73843101e-03f, -1.11269177e-02f, - -1.24149972e-02f, -1.35920411e-02f, -1.46483675e-02f, -1.55754162e-02f, -1.63657097e-02f, -1.70130158e-02f, - -1.75123254e-02f, -1.78599156e-02f, -1.80533642e-02f, -1.80916471e-02f, -1.79749596e-02f, -1.77049199e-02f, - -1.72844059e-02f, -1.67175734e-02f, -1.60098348e-02f, -1.51677846e-02f, -1.41991369e-02f, -1.31126308e-02f, - -1.19180614e-02f, -1.06260158e-02f, -9.24795820e-03f, -7.79599691e-03f, -6.28282689e-03f, -4.72166017e-03f, - -3.12602130e-03f, -1.50971188e-03f, 1.13358008e-04f, 1.72924640e-03f, 3.32419869e-03f, 4.88457483e-03f, - 6.39719332e-03f, 7.84928507e-03f, 9.22860374e-03f, 1.05236737e-02f, 1.17237027e-02f, 1.28187631e-02f, - 1.37999219e-02f, 1.46591627e-02f, 1.53896448e-02f, 1.59855771e-02f, 1.64423748e-02f, 1.67566705e-02f, - 1.69263151e-02f, 1.69504088e-02f, 1.68293192e-02f, 1.65646048e-02f, 1.61591292e-02f, 1.56168830e-02f, - 1.49430466e-02f, 1.41438870e-02f, 1.32267343e-02f, 1.21999194e-02f, 1.10726150e-02f, 9.85491162e-03f, - 8.55755480e-03f, 7.19198626e-03f, 5.77013714e-03f, 4.30443841e-03f, 2.80758857e-03f, 1.29252809e-03f, - -2.27683018e-04f, -1.74000213e-03f, -3.23153173e-03f, -4.68956247e-03f, -6.10171563e-03f, -7.45612506e-03f, - -8.74136426e-03f, -9.94672023e-03f, -1.10621909e-02f, -1.20785406e-02f, -1.29874795e-02f, -1.37816456e-02f, - -1.44546479e-02f, -1.50012468e-02f, -1.54172106e-02f, -1.56995155e-02f, -1.58462779e-02f, -1.58567437e-02f, - -1.57313825e-02f, -1.54717967e-02f, -1.50807184e-02f, -1.45620705e-02f, -1.39207297e-02f, -1.31627253e-02f, - -1.22950111e-02f, -1.13254027e-02f, -1.02626834e-02f, -9.11627932e-03f, -7.89634415e-03f, -6.61364765e-03f, - -5.27939952e-03f, -3.90525708e-03f, -2.50314317e-03f, -1.08517576e-03f, 3.36418391e-04f, 1.74945190e-03f, - 3.14186033e-03f, 4.50178261e-03f, 5.81769448e-03f, 7.07851939e-03f, 8.27365386e-03f, 9.39310326e-03f, - 1.04276320e-02f, 1.13686527e-02f, 1.22085379e-02f, 1.29404450e-02f, 1.35585678e-02f, 1.40580446e-02f, - 1.44350939e-02f, 1.46869568e-02f, 1.48120098e-02f, 1.48096348e-02f, 1.46804295e-02f, 1.44259781e-02f, - 1.40489668e-02f, 1.35531325e-02f, 1.29432014e-02f, 1.22248563e-02f, 1.14046959e-02f, 1.04901687e-02f, - 9.48948107e-03f, 8.41156632e-03f, 7.26596347e-03f, 6.06280447e-03f, 4.81257444e-03f, 3.52622627e-03f, - 2.21492506e-03f, 8.89983592e-04f, -4.37153812e-04f, -1.75513167e-03f, -3.05265494e-03f, -4.31872834e-03f, - -5.54261874e-03f, -6.71396264e-03f, -7.82302244e-03f, -8.86045250e-03f, -9.81773278e-03f, -1.06869351e-02f, - -1.14610023e-02f, -1.21336754e-02f, -1.26995953e-02f, -1.31543908e-02f, -1.34945718e-02f, -1.37177266e-02f, - -1.38224110e-02f, -1.38082286e-02f, -1.36757739e-02f, -1.34266887e-02f, -1.30635886e-02f, -1.25900369e-02f, - -1.20105709e-02f, -1.13305978e-02f, -1.05563538e-02f, -9.69485926e-03f, -8.75389081e-03f, -7.74181164e-03f, - -6.66761679e-03f, -5.54076187e-03f, -4.37111830e-03f, -3.16893052e-03f, -1.94457115e-03f, -7.08705149e-04f, - 5.28079290e-04f, 1.75515870e-03f, 2.96204304e-03f, 4.13848585e-03f, 5.27451557e-03f, 6.36060039e-03f, - 7.38755863e-03f, 8.34692530e-03f, 9.23070802e-03f, 1.00316534e-02f, 1.07432528e-02f, 1.13597680e-02f, - 1.18763350e-02f, 1.22889283e-02f, 1.25944631e-02f, 1.27907515e-02f, 1.28765994e-02f, 1.28517102e-02f, - 1.27167966e-02f, 1.24734480e-02f, 1.21242371e-02f, 1.16725839e-02f, 1.11228281e-02f, 1.04800592e-02f, - 9.75022575e-03f, 8.93990424e-03f, 8.05644990e-03f, 7.10768601e-03f, 6.10205625e-03f, 5.04843878e-03f, - 3.95605458e-03f, 2.83441418e-03f, 1.69331277e-03f, 5.42568186e-04f, -6.07877124e-04f, -1.74818575e-03f, - -2.86860405e-03f, -3.95962685e-03f, -5.01201657e-03f, -6.01690058e-03f, -6.96589716e-03f, -7.85110424e-03f, - -8.66518231e-03f, -9.40145619e-03f, -1.00540095e-02f, -1.06175123e-02f, -1.10876024e-02f, -1.14606062e-02f, - -1.17337519e-02f, -1.19051415e-02f, -1.19737311e-02f, -1.19393909e-02f, -1.18028751e-02f, -1.15657387e-02f, - -1.12305357e-02f, -1.08005049e-02f, -1.02797519e-02f, -9.67318729e-03f, -8.98632838e-03f, -8.22543877e-03f, - -7.39737215e-03f, -6.50950785e-03f, -5.56975395e-03f, -4.58632875e-03f, -3.56792674e-03f, -2.52340823e-03f, - -1.46183597e-03f, -3.92391156e-04f, 6.75701684e-04f, 1.73331709e-03f, 2.77141530e-03f, 3.78118353e-03f, - 4.75407672e-03f, 5.68193005e-03f, 6.55698994e-03f, 7.37195674e-03f, 8.12013345e-03f, 8.79539509e-03f, - 9.39225030e-03f, 9.90597190e-03f, 1.03324819e-02f, 1.06685242e-02f, 1.09116177e-02f, 1.10600973e-02f, - 1.11130936e-02f, 1.10705983e-02f, 1.09333788e-02f, 1.07030445e-02f, 1.03819949e-02f, 9.97335332e-03f, - 9.48107464e-03f, 8.90968434e-03f, 8.26449756e-03f, 7.55132972e-03f, 6.77664458e-03f, 5.94731079e-03f, - 5.07073939e-03f, 4.15462520e-03f, 3.20700306e-03f, 2.23616222e-03f, 1.25050340e-03f, 2.58592562e-04f, - -7.31105992e-04f, -1.71003848e-03f, -2.66991104e-03f, -3.60254805e-03f, -4.50009626e-03f, -5.35500152e-03f, - -6.16013372e-03f, -6.90880302e-03f, -7.59484887e-03f, -8.21267759e-03f, -8.75730297e-03f, -9.22437062e-03f, - -9.61022818e-03f, -9.91196266e-03f, -1.01273334e-02f, -1.02549146e-02f, -1.02939949e-02f, -1.02446487e-02f, - -1.01077102e-02f, -9.88473930e-03f, -9.57804506e-03f, -9.19065219e-03f, -8.72623997e-03f, -8.18914967e-03f, - -7.58431711e-03f, -6.91725624e-03f, -6.19393169e-03f, -5.42085678e-03f, -4.60486090e-03f, -3.75314479e-03f, - -2.87318400e-03f, -1.97263669e-03f, -1.05936420e-03f, -1.41184633e-04f, 7.73935206e-04f, 1.67818033e-03f, - 2.56387121e-03f, 3.42348245e-03f, 4.24972968e-03f, 5.03575853e-03f, 5.77493594e-03f, 6.46117800e-03f, - 7.08885263e-03f, 7.65282423e-03f, 8.14856911e-03f, 8.57214716e-03f, 8.92027019e-03f, 9.19029194e-03f, - 9.38027470e-03f, 9.48895025e-03f, 9.51578399e-03f, 9.46091429e-03f, 9.32518284e-03f, 9.11016180e-03f, - 8.81806173e-03f, 8.45171440e-03f, 8.01466407e-03f, 7.51094572e-03f, 6.94521826e-03f, 6.32261691e-03f, - 5.64875255e-03f, 4.92963671e-03f, 4.17165548e-03f, 3.38149573e-03f, 2.56610069e-03f, 1.73253154e-03f, - 8.88083719e-04f, 4.00140997e-05f, -8.04377007e-04f, -1.63786496e-03f, -2.45336348e-03f, -3.24394120e-03f, - -4.00297149e-03f, -4.72406012e-03f, -5.40122825e-03f, -6.02886353e-03f, -6.60184564e-03f, -7.11547043e-03f, - -7.56567204e-03f, -7.94886879e-03f, -8.26207948e-03f, -8.50298133e-03f, -8.66984745e-03f, -8.76158174e-03f, - -8.77778600e-03f, -8.71866903e-03f, -8.58510255e-03f, -8.37858953e-03f, -8.10125332e-03f, -7.75580633e-03f, - -7.34555568e-03f, -6.87431135e-03f, -6.34642360e-03f, -5.76669768e-03f, -5.14031767e-03f, -4.47294897e-03f, - -3.77043291e-03f, -3.03903272e-03f, -2.28511456e-03f, -1.51527024e-03f, -7.36178447e-04f, 4.54225562e-05f, - 8.22859022e-04f, 1.58943109e-03f, 2.33866278e-03f, 3.06420334e-03f, 3.75990680e-03f, 4.42002538e-03f, - 5.03901750e-03f, 5.61180111e-03f, 6.13366220e-03f, 6.60043272e-03f, 7.00831931e-03f, 7.35414500e-03f, - 7.63524392e-03f, 7.84953557e-03f, 7.99547645e-03f, 8.07218955e-03f, 8.07933095e-03f, 8.01721906e-03f, - 7.88666864e-03f, 7.68919343e-03f, 7.42679720e-03f, 7.10202788e-03f, 6.71802523e-03f, 6.27832934e-03f, - 5.78702253e-03f, 5.24853339e-03f, 4.66776048e-03f, 4.04985033e-03f, 3.40032055e-03f, 2.72486114e-03f, - 2.02943382e-03f, 1.32005555e-03f, 6.02922229e-04f, -1.15810889e-04f, -8.29962401e-04f, -1.53344695e-03f, - -2.22024937e-03f, -2.88460828e-03f, -3.52090915e-03f, -4.12386103e-03f, -4.68844782e-03f, -5.21000854e-03f, - -5.68433641e-03f, -6.10753890e-03f, -6.47629357e-03f, -6.78770430e-03f, -7.03936807e-03f, -7.22944790e-03f, - -7.35662441e-03f, -7.42012069e-03f, -7.41971164e-03f, -7.35573757e-03f, -7.22905724e-03f, -7.04107429e-03f, - -6.79370122e-03f, -6.48940038e-03f, -6.13102314e-03f, -5.72192873e-03f, -5.26590521e-03f, -4.76707464e-03f, - -4.22993214e-03f, -3.65930825e-03f, -3.06022345e-03f, -2.43797793e-03f, -1.79803310e-03f, -1.14594988e-03f, - -4.87389180e-04f, 1.71985886e-04f, 8.26505744e-04f, 1.47057292e-03f, 2.09875564e-03f, 2.70572827e-03f, - 3.28638788e-03f, 3.83592350e-03f, 4.34975506e-03f, 4.82368759e-03f, 5.25383132e-03f, 5.63677359e-03f, - 5.96942535e-03f, 6.24924092e-03f, 6.47405650e-03f, 6.64226721e-03f, 6.75269253e-03f, 6.80469430e-03f, - 6.79815717e-03f, 6.73340631e-03f, 6.61130455e-03f, 6.43322863e-03f, 6.20094526e-03f, 5.91677710e-03f, - 5.58340169e-03f, 5.20393196e-03f, 4.78187614e-03f, 4.32106320e-03f, 3.82565711e-03f, 3.30005613e-03f, - 2.74895362e-03f, 2.17719303e-03f, 1.58978015e-03f, 9.91844057e-04f, 3.88540330e-04f, -2.14916878e-04f, - -8.13361192e-04f, -1.40168257e-03f, -1.97489740e-03f, -2.52818059e-03f, -3.05688539e-03f, -3.55662656e-03f, - -4.02326574e-03f, -4.45296958e-03f, -4.84228652e-03f, -5.18803438e-03f, -5.48755315e-03f, -5.73848611e-03f, - -5.93891991e-03f, -6.08745626e-03f, -6.18305471e-03f, -6.22520840e-03f, -6.21382472e-03f, -6.14928419e-03f, - -6.03244633e-03f, -5.86455879e-03f, -5.64736180e-03f, -5.38296537e-03f, -5.07389363e-03f, -4.72301916e-03f, - -4.33361321e-03f, -3.90915761e-03f, -3.45353173e-03f, -2.97077347e-03f, -2.46516689e-03f, -1.94119584e-03f, - -1.40340595e-03f, -8.56512644e-04f, -3.05232133e-04f, 2.45691031e-04f, 7.91538060e-04f, 1.32763724e-03f, - 1.84949345e-03f, 2.35267547e-03f, 2.83299113e-03f, 3.28645035e-03f, 3.70931698e-03f, 4.09812665e-03f, - 4.44973511e-03f, 4.76135341e-03f, 5.03050354e-03f, 5.25513155e-03f, 5.43353323e-03f, 5.56447821e-03f, - 5.64705544e-03f, 5.68083601e-03f, 5.66583437e-03f, 5.60238431e-03f, 5.49135375e-03f, 5.33391723e-03f, - 5.13169207e-03f, 4.88664671e-03f, 4.60113202e-03f, 4.27780860e-03f, 3.91964875e-03f, 3.52989866e-03f, - 3.11212090e-03f, 2.66999053e-03f, 2.20744344e-03f, 1.72859110e-03f, 1.23756351e-03f, 7.38678150e-04f, - 2.36236760e-04f, -2.65462378e-04f, -7.62072815e-04f, -1.24943395e-03f, -1.72337956e-03f, -2.17993754e-03f, - -2.61530935e-03f, -3.02588421e-03f, -3.40825196e-03f, -3.75935360e-03f, -4.07630652e-03f, -4.35660760e-03f, - -4.59808398e-03f, -4.79883718e-03f, -4.95743843e-03f, -5.07271280e-03f, -5.14393833e-03f, -5.17077608e-03f, - -5.15318763e-03f, -5.09164480e-03f, -4.98686807e-03f, -4.84002285e-03f, -4.65260103e-03f, -4.42642977e-03f, - -4.16366446e-03f, -3.86678300e-03f, -3.53847751e-03f, -3.18177292e-03f, -2.79986847e-03f, -2.39618401e-03f, - -1.97429017e-03f, -1.53788782e-03f, -1.09083664e-03f, -6.36973406e-04f, -1.80264329e-04f, 2.75399352e-04f, - 7.26104424e-04f, 1.16802598e-03f, 1.59744046e-03f, 2.01073128e-03f, 2.40446819e-03f, 2.77538562e-03f, - 3.12044615e-03f, 3.43683203e-03f, 3.72202393e-03f, 3.97374850e-03f, 4.19002854e-03f, 4.36925418e-03f, - 4.51006070e-03f, 4.61152219e-03f, 4.67293053e-03f, 4.69404975e-03f, 4.67490366e-03f, 4.61589307e-03f, - 4.51775252e-03f, 4.38154991e-03f, 4.20868532e-03f, 4.00082377e-03f, 3.75997274e-03f, 3.48836415e-03f, - 3.18851504e-03f, 2.86314343e-03f, 2.51519536e-03f, 2.14776743e-03f, 1.76411750e-03f, 1.36763070e-03f, - 9.61751835e-04f, 5.50052405e-04f, 1.36015058e-04f, -2.76720943e-04f, -6.84698152e-04f, -1.08442387e-03f, - -1.47253691e-03f, -1.84578853e-03f, -2.20105818e-03f, -2.53544188e-03f, -2.84616998e-03f, -3.13076058e-03f, - -3.38689733e-03f, -3.61260297e-03f, -3.80606518e-03f, -3.96589267e-03f, -4.09087232e-03f, -4.18013173e-03f, - -4.23315965e-03f, -4.24970953e-03f, -4.22981560e-03f, -4.17392494e-03f, -4.08267808e-03f, -3.95709577e-03f, - -3.79845153e-03f, -3.60829670e-03f, -3.38844338e-03f, -3.14094669e-03f, -2.86809742e-03f, -2.57237442e-03f, - -2.25643831e-03f, -1.92312165e-03f, -1.57535841e-03f, -1.21624129e-03f, -8.48868370e-04f, -4.76457354e-04f, - -1.02227062e-04f, 2.70659894e-04f, 6.38948957e-04f, 9.99596773e-04f, 1.34950884e-03f, 1.68579412e-03f, - 2.00565112e-03f, 2.30644176e-03f, 2.58570970e-03f, 2.84121989e-03f, 3.07087670e-03f, 3.27296771e-03f, - 3.44584695e-03f, 3.58825627e-03f, 3.69915439e-03f, 3.77779535e-03f, 3.82369144e-03f, 3.83666312e-03f, - 3.81678507e-03f, 3.76444486e-03f, 3.68027755e-03f, 3.56519883e-03f, 3.42038694e-03f, 3.24725992e-03f, - 3.04745181e-03f, 2.82287635e-03f, 2.57555610e-03f, 2.30778342e-03f, 2.02193938e-03f, 1.72060684e-03f, - 1.40642226e-03f, 1.08218540e-03f, 7.50708128e-04f, 4.14852040e-04f, 7.75468400e-05f, -2.58336678e-04f, - -5.89954675e-04f, -9.14464553e-04f, -1.22917409e-03f, -1.53142096e-03f, -1.81874942e-03f, -2.08875765e-03f, - -2.33925204e-03f, -2.56824046e-03f, -2.77387464e-03f, -2.95457151e-03f, -3.10891286e-03f, -3.23576957e-03f, - -3.33422309e-03f, -3.40361730e-03f, -3.44352432e-03f, -3.45380945e-03f, -3.43454926e-03f, -3.38612359e-03f, - -3.30910238e-03f, -3.20434413e-03f, -3.07289782e-03f, -2.91605448e-03f, -2.73534798e-03f, -2.53242439e-03f, - -2.30918427e-03f, -2.06766744e-03f, -1.81002532e-03f, -1.53857461e-03f, -1.25572213e-03f, -9.63956082e-04f, - -6.65804929e-04f, -3.63875198e-04f, -6.07622519e-05f, 2.40955893e-04f, 5.38685581e-04f, 8.29936911e-04f, - 1.11224977e-03f, 1.38328230e-03f, 1.64080028e-03f, 1.88265574e-03f, 2.10694670e-03f, 2.31181334e-03f, - 2.49567938e-03f, 2.65707799e-03f, 2.79477329e-03f, 2.90778929e-03f, 2.99526804e-03f, 3.05666792e-03f, - 3.09159989e-03f, 3.09996074e-03f, 3.08183486e-03f, 3.03757314e-03f, 2.96768997e-03f, 2.87296391e-03f, - 2.75438271e-03f, 2.61305979e-03f, 2.45041225e-03f, 2.26792371e-03f, 2.06728115e-03f, 1.85034398e-03f, - 1.61901728e-03f, 1.37543970e-03f, 1.12168235e-03f, 8.60048928e-04f, 5.92781787e-04f, 3.22217129e-04f, - 5.06437951e-05f, -2.19547817e-04f, -4.86132510e-04f, -7.46817210e-04f, -9.99443627e-04f, -1.24188233e-03f, - -1.47217245e-03f, -1.68839648e-03f, -1.88883105e-03f, -2.07184785e-03f, -2.23601745e-03f, -2.38006048e-03f, - -2.50288118e-03f, -2.60358292e-03f, -2.68144174e-03f, -2.73595307e-03f, -2.76679595e-03f, -2.77388624e-03f, - -2.75729794e-03f, -2.71735188e-03f, -2.65451985e-03f, -2.56952130e-03f, -2.46319204e-03f, -2.33660956e-03f, - -2.19096493e-03f, -2.02765268e-03f, -1.84815939e-03f, -1.65412932e-03f, -1.44731483e-03f, -1.22956426e-03f, - -1.00280075e-03f, -7.69022668e-04f, -5.30268510e-04f, -2.88586883e-04f, -4.60956253e-05f, 1.95186584e-04f, - 4.33161045e-04f, 6.65873263e-04f, 8.91328897e-04f, 1.10770620e-03f, 1.31316296e-03f, 1.50610067e-03f, - 1.68489795e-03f, 1.84814923e-03f, 1.99458512e-03f, 2.12304250e-03f, 2.23258384e-03f, 2.32237953e-03f, - 2.39181962e-03f, 2.44043032e-03f, 2.46796938e-03f, 2.47430968e-03f, 2.45957831e-03f, 2.42401283e-03f, - 2.36808884e-03f, 2.29238471e-03f, 2.19773378e-03f, 2.08501666e-03f, 1.95534528e-03f, 1.80993801e-03f, - 1.65014053e-03f, 1.47739854e-03f, 1.29329221e-03f, 1.09944593e-03f, 8.97596290e-04f, 6.89486470e-04f, - 4.76967544e-04f, 2.61847472e-04f, 4.59979030e-05f, -1.68770369e-04f, -3.80612759e-04f, -5.87744421e-04f, - -7.88452414e-04f, -9.81081718e-04f, -1.16402219e-03f, -1.33580811e-03f, -1.49504859e-03f, -1.64047131e-03f, - -1.77095587e-03f, -1.88548340e-03f, -1.98318254e-03f, -2.06335667e-03f, -2.12544333e-03f, -2.16903096e-03f, - -2.19389731e-03f, -2.19994674e-03f, -2.18726700e-03f, -2.15609170e-03f, -2.10683457e-03f, -2.04002290e-03f, - -1.95633800e-03f, -1.85665258e-03f, -1.74189023e-03f, -1.61313165e-03f, -1.47159921e-03f, -1.31856217e-03f, - -1.15541374e-03f, -9.83590913e-04f, -8.04645529e-04f, -6.20138811e-04f, -4.31664744e-04f, -2.40859759e-04f, - -4.93718861e-05f, 1.41183920e-04f, 3.29184443e-04f, 5.13049545e-04f, 6.91252710e-04f, 8.62329668e-04f, - 1.02486089e-03f, 1.17753306e-03f, 1.31912530e-03f, 1.44851584e-03f, 1.56468190e-03f, 1.66675270e-03f, - 1.75393226e-03f, 1.82562545e-03f, 1.88129935e-03f, 1.92062935e-03f, 1.94336360e-03f, 1.94946381e-03f, - 1.93898469e-03f, 1.91211060e-03f, 1.86925265e-03f, 1.81081128e-03f, 1.73745800e-03f, 1.64989979e-03f, - 1.54896085e-03f, 1.43565148e-03f, 1.31095906e-03f, 1.17607031e-03f, 1.03219054e-03f, 8.80596006e-04f, - 7.22634695e-04f, 5.59715925e-04f, 3.93223384e-04f, 2.24602808e-04f, 5.53223372e-05f, -1.13204206e-04f, - -2.79527886e-04f, -4.42273875e-04f, -6.00090187e-04f, -7.51646708e-04f, -8.95738714e-04f, -1.03117771e-03f, - -1.15687770e-03f, -1.27187587e-03f, -1.37523688e-03f, -1.46618576e-03f, -1.54403989e-03f, -1.60825931e-03f, - -1.65836399e-03f, -1.69405240e-03f, -1.71514183e-03f, -1.72154028e-03f, -1.71331327e-03f, -1.69063272e-03f, - -1.65381037e-03f, -1.60326168e-03f, -1.53948863e-03f, -1.46318779e-03f, -1.37503217e-03f, -1.27591969e-03f, - -1.16672308e-03f, -1.04846883e-03f, -9.22232848e-04f, -7.89108246e-04f, -6.50329911e-04f, -5.07057241e-04f, - -3.60579584e-04f, -2.12138548e-04f, -6.30166060e-05f, 8.55107333e-05f, 2.32212191e-04f, 3.75851456e-04f, - 5.15213418e-04f, 6.49182851e-04f, 7.76642588e-04f, 8.96585347e-04f, 1.00803198e-03f, 1.11010987e-03f, - 1.20203475e-03f, 1.28308439e-03f, 1.35268783e-03f, 1.41030687e-03f, 1.45558664e-03f, 1.48819124e-03f, - 1.50798717e-03f, 1.51486502e-03f, 1.50888467e-03f, 1.49022209e-03f, 1.45906012e-03f, 1.41583581e-03f, - 1.36095722e-03f, 1.29499749e-03f, 1.21859138e-03f, 1.13249419e-03f, 1.03745344e-03f, 9.34384957e-04f, - 8.24209226e-04f, 7.07921644e-04f, 5.86535461e-04f, 4.61118668e-04f, 3.32797940e-04f, 2.02615430e-04f, - 7.17560319e-05f, -5.87215139e-05f, -1.87700771e-04f, -3.14093799e-04f, -4.36855019e-04f, -5.54982470e-04f, - -6.67514567e-04f, -7.73539543e-04f, -8.72216549e-04f, -9.62754726e-04f, -1.04446836e-03f, -1.11673823e-03f, - -1.17901020e-03f, -1.23084835e-03f, -1.27191263e-03f, -1.30189831e-03f, -1.32066941e-03f, -1.32816613e-03f, - -1.32437715e-03f, -1.30944714e-03f, -1.28360668e-03f, -1.24710492e-03f, -1.20038313e-03f, -1.14391116e-03f, - -1.07822250e-03f, -1.00394823e-03f, -9.21799577e-04f, -8.32520513e-04f, -7.36916195e-04f, -6.35853312e-04f, - -5.30218398e-04f, -4.20950684e-04f, -3.08981087e-04f, -1.95310152e-04f, -8.08721649e-05f, 3.33481785e-05f, - 1.46369769e-04f, 2.57271691e-04f, 3.65123878e-04f, 4.69053422e-04f, 5.68205019e-04f, 6.61777482e-04f, - 7.49035427e-04f, 8.29295760e-04f, 9.01919035e-04f, 9.66370937e-04f, 1.02218113e-03f, 1.06892877e-03f, - 1.10630552e-03f, 1.13406370e-03f, 1.15204451e-03f, 1.16019052e-03f, 1.15848806e-03f, 1.14706630e-03f, - 1.12606449e-03f, 1.09574589e-03f, 1.05645362e-03f, 1.00859266e-03f, 9.52601766e-04f, 8.89057609e-04f, - 8.18535938e-04f, 7.41697389e-04f, 6.59241262e-04f, 5.71884368e-04f, 4.80414698e-04f, 3.85677252e-04f, - 2.88406796e-04f, 1.89536836e-04f, 8.98491837e-05f, -9.79888746e-06f, -1.08531507e-04f, -2.05575498e-04f, - -3.00092231e-04f, -3.91327952e-04f, -4.78537671e-04f, -5.61003964e-04f, -6.38090388e-04f, -7.09209697e-04f, - -7.73747838e-04f, -8.31297964e-04f, -8.81364804e-04f, -9.23641236e-04f, -9.57793553e-04f, -9.83624619e-04f, - -1.00098424e-03f, -1.00979404e-03f, -1.01003977e-03f, -1.00180772e-03f, -9.85219816e-04f, -9.60506778e-04f, - -9.27905874e-04f, -8.87790902e-04f, -8.40553609e-04f, -7.86632276e-04f, -7.26559669e-04f, -6.60872173e-04f, - -5.90177860e-04f, -5.15099219e-04f, -4.36341554e-04f, -3.54526447e-04f, -2.70436804e-04f, -1.84757234e-04f, - -9.82406108e-05f, -1.16228429e-05f, 7.44116225e-05f, 1.59099493e-04f, 2.41739119e-04f, 3.21707034e-04f, - 3.98276352e-04f, 4.70887555e-04f, 5.38973046e-04f, 6.01940918e-04f, 6.59368174e-04f, 7.10783030e-04f, - 7.55802336e-04f, 7.94127086e-04f, 8.25478803e-04f, 8.49639386e-04f, 8.66487952e-04f, 8.75935969e-04f, - 8.77948893e-04f, 8.72611584e-04f, 8.59994515e-04f, 8.40271458e-04f, 8.13696181e-04f, 7.80491851e-04f, - 7.41053306e-04f, 6.95727202e-04f, 6.44936090e-04f, 5.89181503e-04f, 5.28946796e-04f, 4.64790448e-04f, - 3.97272420e-04f, 3.27000597e-04f, 2.54559578e-04f, 1.80597276e-04f, 1.05760446e-04f, 3.06209047e-05f, - -4.41172003e-05f, -1.17884760e-04f, -1.90032814e-04f, -2.60000039e-04f, -3.27213235e-04f, -3.91110007e-04f, - -4.51226928e-04f, -5.07042112e-04f, -5.58194586e-04f, -6.04189222e-04f, -6.44816381e-04f, -6.79653847e-04f, - -7.08557315e-04f, -7.31282579e-04f, -7.47702169e-04f, -7.57731688e-04f, -7.61359812e-04f, -7.58589885e-04f, - -7.49503361e-04f, -7.34226582e-04f, -7.12935677e-04f, -6.85882645e-04f, -6.53307567e-04f, -6.15569562e-04f, - -5.72978650e-04f, -5.25977418e-04f, -4.74963705e-04f, -4.20426590e-04f, -3.62819514e-04f, -3.02647353e-04f, - -2.40497241e-04f, -1.76810216e-04f, -1.12210871e-04f, -4.71976690e-05f, 1.76624641e-05f, 8.18440593e-05f, - 1.44804207e-04f, 2.06021410e-04f, 2.65025446e-04f, 3.21327783e-04f, 3.74487008e-04f, 4.24062432e-04f, - 4.69715655e-04f, 5.11042943e-04f, 5.47794530e-04f, 5.79655168e-04f, 6.06446384e-04f, 6.27934546e-04f, - 6.44010762e-04f, 6.54614698e-04f, 6.59636425e-04f, 6.59157826e-04f, 6.53158826e-04f, 6.41794049e-04f, - 6.25154916e-04f, 6.03470855e-04f, 5.76917242e-04f, 5.45789736e-04f, 5.10368292e-04f, 4.70998661e-04f, - 4.28021656e-04f, 3.81834126e-04f, 3.32863326e-04f, 2.81489629e-04f, 2.28231239e-04f, 1.73484261e-04f, - 1.17756607e-04f, 6.14881351e-05f, 5.17778269e-06f, -5.07352374e-05f, -1.05745987e-04f, -1.59454662e-04f, - -2.11394268e-04f, -2.61151905e-04f, -3.08351703e-04f, -3.52598590e-04f, -3.93545002e-04f, -4.30916147e-04f, - -4.64387406e-04f, -4.93756593e-04f, -5.18755281e-04f, -5.39265493e-04f, -5.55137934e-04f, -5.66259303e-04f, - -5.72606783e-04f, -5.74140344e-04f, -5.70903292e-04f, -5.62934741e-04f, -5.50388898e-04f, -5.33351962e-04f, - -5.12028510e-04f, -4.86612455e-04f, -4.57392981e-04f, -4.24578939e-04f, -3.88503808e-04f, -3.49487518e-04f, - -3.07895836e-04f, -2.64036522e-04f, -2.18356445e-04f, -1.71198300e-04f, -1.22998901e-04f, -7.41392080e-05f, - -2.50280393e-05f, 2.38852047e-05f, 7.22663332e-05f, 1.19659647e-04f, 1.65718806e-04f, 2.10055385e-04f, - 2.52324173e-04f, 2.92190427e-04f, 3.29337577e-04f, 3.63510150e-04f, 3.94385715e-04f, 4.21803288e-04f, - 4.45519433e-04f, 4.65391876e-04f, 4.81270460e-04f, 4.93057625e-04f, 5.00688030e-04f, 5.04121708e-04f, - 5.03379627e-04f, 4.98485604e-04f, 4.89499566e-04f, 4.76539317e-04f, 4.59760023e-04f, 4.39274612e-04f, - 4.15334876e-04f, 3.88103885e-04f, 3.57902146e-04f, 3.24908089e-04f, 2.89490480e-04f, 2.51922687e-04f, - 2.12512220e-04f, 1.71637404e-04f, 1.29609890e-04f, 8.67866183e-05f, 4.35312276e-05f, 1.98808307e-07f, - -4.28589070e-05f, -8.52865394e-05f, -1.26765698e-04f, -1.66922292e-04f, -2.05456466e-04f, -2.42095652e-04f, - -2.76487494e-04f, -3.08425602e-04f, -3.37638832e-04f, -3.63923042e-04f, -3.87022898e-04f, -4.06875144e-04f, - -4.23245129e-04f, -4.36071615e-04f, -4.45236993e-04f, -4.50724682e-04f, -4.52491230e-04f, -4.50548104e-04f, - -4.44936790e-04f, -4.35725612e-04f, -4.22987381e-04f, -4.06882738e-04f, -3.87548587e-04f, -3.65123104e-04f, - -3.39860288e-04f, -3.11947486e-04f, -2.81618569e-04f, -2.49166817e-04f, -2.14824344e-04f, -1.78876370e-04f, - -1.41684861e-04f, -1.03466427e-04f, -6.45996088e-05f, -2.53738050e-05f, 1.39035721e-05f, 5.28977578e-05f, - 9.13010773e-05f, 1.28809554e-04f, 1.65139924e-04f, 2.00005346e-04f, 2.33095696e-04f, 2.64232233e-04f, - 2.93070034e-04f, 3.19508024e-04f, 3.43252648e-04f, 3.64165224e-04f, 3.82074036e-04f, 3.96868082e-04f, - 4.08408250e-04f, 4.16671952e-04f, 4.21556517e-04f, 4.23035822e-04f, 4.21172111e-04f, 4.15928838e-04f, - 4.07377025e-04f, 3.95568598e-04f, 3.80628038e-04f, 3.62729177e-04f, 3.41921136e-04f, 3.18489958e-04f, - 2.92497406e-04f, 2.64266550e-04f, 2.33955571e-04f, 2.01809261e-04f, 1.68092145e-04f, 1.33141461e-04f, - 9.71043460e-05f, 6.03452880e-05f, 2.31264055e-05f, -1.43105089e-05f, -5.15607083e-05f, -8.84833364e-05f, - -1.24679461e-04f, -1.59910519e-04f, -1.93952723e-04f, -2.26496145e-04f, -2.57307566e-04f, -2.86175538e-04f, - -3.12853472e-04f, -3.37140613e-04f, -3.58914997e-04f, -3.77932329e-04f, -3.94117065e-04f, -4.07317063e-04f, - -4.17422308e-04f, -4.24419479e-04f, -4.28161231e-04f, -4.28700484e-04f, -4.26016659e-04f, -4.20088126e-04f, - -4.11009185e-04f, -3.98835037e-04f, -3.83585114e-04f, -3.65493072e-04f, -3.44616197e-04f, -3.21064387e-04f, - -2.95119418e-04f, -2.66863117e-04f, -2.36549174e-04f, -2.04391686e-04f, -1.70585806e-04f, -1.35432614e-04f, - -9.91006984e-05f, -6.19152828e-05f, -2.41012311e-05f, 1.40621144e-05f, 5.22867497e-05f, 9.03199843e-05f, - 1.27917614e-04f, 1.64740292e-04f, 2.00634478e-04f, 2.35261402e-04f, 2.68377430e-04f, 2.99818019e-04f, - 3.29273634e-04f, 3.56562766e-04f, 3.81532332e-04f, 4.03948113e-04f, 4.23655375e-04f, 4.40488930e-04f, - 4.54376777e-04f, 4.65137195e-04f, 4.72679704e-04f, 4.77014073e-04f, 4.77982201e-04f, 4.75625277e-04f, - 4.69878507e-04f, 4.60802987e-04f, 4.48367418e-04f, 4.32641679e-04f, 4.13709630e-04f, 3.91634147e-04f, - 3.66512902e-04f, 3.38481392e-04f, 3.07634938e-04f, 2.74189182e-04f, 2.38229594e-04f, 1.99985879e-04f, - 1.59632210e-04f, 1.17351364e-04f, 7.33404728e-05f, 2.78844831e-05f, -1.89099461e-05f, -6.67343638e-05f, - -1.15367449e-04f, -1.64649983e-04f, -2.14224348e-04f, -2.64019844e-04f, -3.13654244e-04f, -3.62990333e-04f, - -4.11800705e-04f, -4.59821928e-04f, -5.06946486e-04f, -5.52847863e-04f, -5.97397068e-04f, -6.40454770e-04f, - -6.81765968e-04f, -7.21210131e-04f, -7.58634477e-04f, -7.93939572e-04f, -8.26964876e-04f, -8.57585335e-04f, - -8.85733438e-04f, -9.11351007e-04f, -9.34300512e-04f, -9.54617442e-04f, -9.72159416e-04f, -9.87012089e-04f, - -9.99095133e-04f, -1.00846242e-03f, -1.01506022e-03f, -1.01897105e-03f, -1.02021427e-03f, -1.01887259e-03f, - -1.01497557e-03f, -1.00861358e-03f, -9.99877741e-04f, -9.88823136e-04f, -9.75617693e-04f, -9.60303769e-04f, - -9.43035535e-04f, -9.23922797e-04f, -9.03105429e-04f, -8.80708716e-04f, -8.56853281e-04f, -8.31685264e-04f, - -8.05348207e-04f, -7.77961627e-04f, -7.49713086e-04f, -7.20674604e-04f, -6.91032783e-04f, -6.60888020e-04f, - -6.30372917e-04f, -5.99673349e-04f, -5.68830563e-04f, -5.38013304e-04f, -5.07353303e-04f, -4.76915043e-04f, - -4.46832926e-04f, -4.17179291e-04f, -3.88083307e-04f, -3.59575024e-04f, -3.31820735e-04f, -3.04804303e-04f, - -2.78616041e-04f, -2.53335964e-04f, -2.28986996e-04f, -2.05619529e-04f, -1.83318449e-04f, -1.61979425e-04f, - -1.41791423e-04f, -1.22648816e-04f, -1.04625498e-04f, -8.77122910e-05f, -7.18653457e-05f, -5.71787106e-05f, - -4.34807639e-05f, -3.09618857e-05f, -1.94074401e-05f, -8.88017971e-06f, 6.09625220e-07f, 9.14020334e-06f, - 1.67805558e-05f, 2.35369965e-05f, 2.94278194e-05f, 3.45049751e-05f, 3.88373828e-05f, 4.24291966e-05f, - 4.53445665e-05f, 4.76965834e-05f, 4.93395567e-05f, 5.05392111e-05f, 5.12257065e-05f, 5.14579340e-05f, - 5.12651750e-05f, 5.07312551e-05f, 4.98486765e-05f, 4.87082573e-05f, 4.73439631e-05f, 4.56740817e-05f, - 4.38653618e-05f, 4.19399075e-05f, 3.99125668e-05f, 3.77616021e-05f, 3.56135997e-05f, 3.33554815e-05f, - 3.11656899e-05f, 2.89038150e-05f, 2.67281634e-05f, 2.46192762e-05f, 2.24899205e-05f, 2.04698700e-05f, - 1.84927655e-05f, 1.66762886e-05f, 1.49393771e-05f, 1.32258081e-05f, 1.16985586e-05f, 1.01874391e-05f, - 8.99882100e-06f, 7.61267073e-06f, 6.57702907e-06f, 5.59829210e-06f, 4.27698546e-06f, 1.03248674e-05f, -}; - -// -// polyphase filter -// -static const int SRC_PHASEBITS = 8; -static const int SRC_PHASES = (1 << SRC_PHASEBITS); -static const int SRC_FRACBITS = 32 - SRC_PHASEBITS; -static const uint32_t SRC_FRACMASK = (1 << SRC_FRACBITS) - 1; - -static const float QFRAC_TO_FLOAT = 1.0f / (1 << SRC_FRACBITS); -static const float Q32_TO_FLOAT = 1.0f / (1ULL << 32); - -// blocking size in frames, chosen so block processing fits in L1 cache -static const int SRC_BLOCK = 1024; - -#define lo32(a) ((uint32_t)(a)) -#define hi32(a) ((int32_t)((a) >> 32)) +// high/low part of int64_t +#define LO32(a) ((uint32_t)(a)) +#define HI32(a) ((int32_t)((a) >> 32)) // // Portable aligned malloc/free @@ -610,8 +69,8 @@ static void cubicInterpolation(const float* input, float* output, int inputSize, // Lagrange interpolation using Farrow structure for (int j = 0; j < outputSize; j++) { - int32_t i = hi32(offset); - uint32_t f = lo32(offset); + int32_t i = HI32(offset); + uint32_t f = LO32(offset); // values outside the window are zero float x0 = (i - 1 < 0) ? 0.0f : input[i - 1]; @@ -649,7 +108,7 @@ int AudioSRC::createRationalFilter(int upFactor, int downFactor, float gain) { numTaps = (numCoefs + upFactor - 1) / upFactor; gain *= (float)oldCoefs / numCoefs; } - numTaps = (numTaps + 3) & ~3; // SIMD4 + numTaps = (numTaps + 7) & ~7; // SIMD8 // interpolate the coefficients of the prototype filter float* tempFilter = new float[numTaps * numPhases]; @@ -658,7 +117,7 @@ int AudioSRC::createRationalFilter(int upFactor, int downFactor, float gain) { cubicInterpolation(prototypeFilter, tempFilter, prototypeCoefs, numCoefs, gain); // create the polyphase filter - _polyphaseFilter = (float*)aligned_malloc(numTaps * numPhases * sizeof(float), 16); // SIMD4 + _polyphaseFilter = (float*)aligned_malloc(numTaps * numPhases * sizeof(float), 32); // SIMD8 // rearrange into polyphase form, ordered by use for (int i = 0; i < numPhases; i++) { @@ -699,7 +158,7 @@ int AudioSRC::createIrrationalFilter(int upFactor, int downFactor, float gain) { numTaps = (numCoefs + upFactor - 1) / upFactor; gain *= (float)oldCoefs / numCoefs; } - numTaps = (numTaps + 3) & ~3; // SIMD4 + numTaps = (numTaps + 7) & ~7; // SIMD8 // interpolate the coefficients of the prototype filter float* tempFilter = new float[numTaps * numPhases]; @@ -708,7 +167,7 @@ int AudioSRC::createIrrationalFilter(int upFactor, int downFactor, float gain) { cubicInterpolation(prototypeFilter, tempFilter, prototypeCoefs, numCoefs, gain); // create the polyphase filter, with extra phase at the end to simplify coef interpolation - _polyphaseFilter = (float*)aligned_malloc(numTaps * (numPhases + 1) * sizeof(float), 16); // SIMD4 + _polyphaseFilter = (float*)aligned_malloc(numTaps * (numPhases + 1) * sizeof(float), 32); // SIMD8 // rearrange into polyphase form, ordered by fractional delay for (int phase = 0; phase < numPhases; phase++) { @@ -741,14 +200,14 @@ int AudioSRC::createIrrationalFilter(int upFactor, int downFactor, float gain) { #include -int AudioSRC::multirateFilter1(const float* input0, float* output0, int inputFrames) { +int AudioSRC::multirateFilter1_SSE(const float* input0, float* output0, int inputFrames) { int outputFrames = 0; - assert((_numTaps & 0x3) == 0); // SIMD4 + assert(_numTaps % 4 == 0); // SIMD4 if (_step == 0) { // rational - int32_t i = hi32(_offset); + int32_t i = HI32(_offset); while (i < inputFrames) { @@ -761,7 +220,7 @@ int AudioSRC::multirateFilter1(const float* input0, float* output0, int inputFra //float coef = c0[j]; __m128 coef0 = _mm_loadu_ps(&c0[j]); - //acc0 += input0[i + j] * coef; + //acc += input[i + j] * coef; acc0 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input0[i + j]), coef0), acc0); } @@ -781,10 +240,10 @@ int AudioSRC::multirateFilter1(const float* input0, float* output0, int inputFra } else { // irrational - while (hi32(_offset) < inputFrames) { + while (HI32(_offset) < inputFrames) { - int32_t i = hi32(_offset); - uint32_t f = lo32(_offset); + int32_t i = HI32(_offset); + uint32_t f = LO32(_offset); uint32_t phase = f >> SRC_FRACBITS; __m128 frac = _mm_set1_ps((f & SRC_FRACMASK) * QFRAC_TO_FLOAT); @@ -802,7 +261,7 @@ int AudioSRC::multirateFilter1(const float* input0, float* output0, int inputFra coef1 = _mm_sub_ps(coef1, coef0); coef0 = _mm_add_ps(_mm_mul_ps(coef1, frac), coef0); - //acc0 += input0[i + j] * coef; + //acc += input[i + j] * coef; acc0 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input0[i + j]), coef0), acc0); } @@ -821,98 +280,116 @@ int AudioSRC::multirateFilter1(const float* input0, float* output0, int inputFra return outputFrames; } +int AudioSRC::multirateFilter2_SSE(const float* input0, const float* input1, float* output0, float* output1, int inputFrames) { + int outputFrames = 0; + + assert(_numTaps % 4 == 0); // SIMD4 + + if (_step == 0) { // rational + + int32_t i = HI32(_offset); + + while (i < inputFrames) { + + const float* c0 = &_polyphaseFilter[_numTaps * _phase]; + + __m128 acc0 = _mm_setzero_ps(); + __m128 acc1 = _mm_setzero_ps(); + + for (int j = 0; j < _numTaps; j += 4) { + + //float coef = c0[j]; + __m128 coef0 = _mm_loadu_ps(&c0[j]); + + //acc += input[i + j] * coef; + acc0 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input0[i + j]), coef0), acc0); + acc1 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input1[i + j]), coef0), acc1); + } + + // horizontal sum + acc0 = _mm_add_ps(acc0, _mm_movehl_ps(acc0, acc0)); + acc1 = _mm_add_ps(acc1, _mm_movehl_ps(acc1, acc1)); + acc0 = _mm_add_ss(acc0, _mm_shuffle_ps(acc0, acc0, _MM_SHUFFLE(0,0,0,1))); + acc1 = _mm_add_ss(acc1, _mm_shuffle_ps(acc1, acc1, _MM_SHUFFLE(0,0,0,1))); + + _mm_store_ss(&output0[outputFrames], acc0); + _mm_store_ss(&output1[outputFrames], acc1); + outputFrames += 1; + + i += _stepTable[_phase]; + if (++_phase == _upFactor) { + _phase = 0; + } + } + _offset = (int64_t)(i - inputFrames) << 32; + + } else { // irrational + + while (HI32(_offset) < inputFrames) { + + int32_t i = HI32(_offset); + uint32_t f = LO32(_offset); + + uint32_t phase = f >> SRC_FRACBITS; + __m128 frac = _mm_set1_ps((f & SRC_FRACMASK) * QFRAC_TO_FLOAT); + + const float* c0 = &_polyphaseFilter[_numTaps * (phase + 0)]; + const float* c1 = &_polyphaseFilter[_numTaps * (phase + 1)]; + + __m128 acc0 = _mm_setzero_ps(); + __m128 acc1 = _mm_setzero_ps(); + + for (int j = 0; j < _numTaps; j += 4) { + + //float coef = c0[j] + frac * (c1[j] - c0[j]); + __m128 coef0 = _mm_loadu_ps(&c0[j]); + __m128 coef1 = _mm_loadu_ps(&c1[j]); + coef1 = _mm_sub_ps(coef1, coef0); + coef0 = _mm_add_ps(_mm_mul_ps(coef1, frac), coef0); + + //acc += input[i + j] * coef; + acc0 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input0[i + j]), coef0), acc0); + acc1 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input1[i + j]), coef0), acc1); + } + + // horizontal sum + acc0 = _mm_add_ps(acc0, _mm_movehl_ps(acc0, acc0)); + acc1 = _mm_add_ps(acc1, _mm_movehl_ps(acc1, acc1)); + acc0 = _mm_add_ss(acc0, _mm_shuffle_ps(acc0, acc0, _MM_SHUFFLE(0,0,0,1))); + acc1 = _mm_add_ss(acc1, _mm_shuffle_ps(acc1, acc1, _MM_SHUFFLE(0,0,0,1))); + + _mm_store_ss(&output0[outputFrames], acc0); + _mm_store_ss(&output1[outputFrames], acc1); + outputFrames += 1; + + _offset += _step; + } + _offset -= (int64_t)inputFrames << 32; + } + + return outputFrames; +} + +// +// Runtime CPU dispatch +// + +#include "CPUDetect.h" + +int AudioSRC::multirateFilter1(const float* input0, float* output0, int inputFrames) { + + static auto f = cpuSupportsAVX2() ? &AudioSRC::multirateFilter1_AVX2 : &AudioSRC::multirateFilter1_SSE; + return (this->*f)(input0, output0, inputFrames); // dispatch +} + int AudioSRC::multirateFilter2(const float* input0, const float* input1, float* output0, float* output1, int inputFrames) { - int outputFrames = 0; - assert((_numTaps & 0x3) == 0); // SIMD4 - - if (_step == 0) { // rational - - int32_t i = hi32(_offset); - - while (i < inputFrames) { - - const float* c0 = &_polyphaseFilter[_numTaps * _phase]; - - __m128 acc0 = _mm_setzero_ps(); - __m128 acc1 = _mm_setzero_ps(); - - for (int j = 0; j < _numTaps; j += 4) { - - //float coef = c0[j]; - __m128 coef0 = _mm_loadu_ps(&c0[j]); - - //acc0 += input0[i + j] * coef; - acc0 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input0[i + j]), coef0), acc0); - acc1 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input1[i + j]), coef0), acc1); - } - - // horizontal sum - acc0 = _mm_add_ps(acc0, _mm_movehl_ps(acc0, acc0)); - acc1 = _mm_add_ps(acc1, _mm_movehl_ps(acc1, acc1)); - acc0 = _mm_add_ss(acc0, _mm_shuffle_ps(acc0, acc0, _MM_SHUFFLE(0,0,0,1))); - acc1 = _mm_add_ss(acc1, _mm_shuffle_ps(acc1, acc1, _MM_SHUFFLE(0,0,0,1))); - - _mm_store_ss(&output0[outputFrames], acc0); - _mm_store_ss(&output1[outputFrames], acc1); - outputFrames += 1; - - i += _stepTable[_phase]; - if (++_phase == _upFactor) { - _phase = 0; - } - } - _offset = (int64_t)(i - inputFrames) << 32; - - } else { // irrational - - while (hi32(_offset) < inputFrames) { - - int32_t i = hi32(_offset); - uint32_t f = lo32(_offset); - - uint32_t phase = f >> SRC_FRACBITS; - __m128 frac = _mm_set1_ps((f & SRC_FRACMASK) * QFRAC_TO_FLOAT); - - const float* c0 = &_polyphaseFilter[_numTaps * (phase + 0)]; - const float* c1 = &_polyphaseFilter[_numTaps * (phase + 1)]; - - __m128 acc0 = _mm_setzero_ps(); - __m128 acc1 = _mm_setzero_ps(); - - for (int j = 0; j < _numTaps; j += 4) { - - //float coef = c0[j] + frac * (c1[j] - c0[j]); - __m128 coef0 = _mm_loadu_ps(&c0[j]); - __m128 coef1 = _mm_loadu_ps(&c1[j]); - coef1 = _mm_sub_ps(coef1, coef0); - coef0 = _mm_add_ps(_mm_mul_ps(coef1, frac), coef0); - - //acc0 += input0[i + j] * coef; - acc0 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input0[i + j]), coef0), acc0); - acc1 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input1[i + j]), coef0), acc1); - } - - // horizontal sum - acc0 = _mm_add_ps(acc0, _mm_movehl_ps(acc0, acc0)); - acc1 = _mm_add_ps(acc1, _mm_movehl_ps(acc1, acc1)); - acc0 = _mm_add_ss(acc0, _mm_shuffle_ps(acc0, acc0, _MM_SHUFFLE(0,0,0,1))); - acc1 = _mm_add_ss(acc1, _mm_shuffle_ps(acc1, acc1, _MM_SHUFFLE(0,0,0,1))); - - _mm_store_ss(&output0[outputFrames], acc0); - _mm_store_ss(&output1[outputFrames], acc1); - outputFrames += 1; - - _offset += _step; - } - _offset -= (int64_t)inputFrames << 32; - } - - return outputFrames; + static auto f = cpuSupportsAVX2() ? &AudioSRC::multirateFilter2_AVX2 : &AudioSRC::multirateFilter2_SSE; + return (this->*f)(input0, input1, output0, output1, inputFrames); // dispatch } // convert int16_t to float, deinterleave stereo -void AudioSRC::convertInputFromInt16(const int16_t* input, float** outputs, int numFrames) { +void AudioSRC::convertInput(const int16_t* input, float** outputs, int numFrames) { __m128 scale = _mm_set1_ps(1/32768.0f); if (_numChannels == 1) { @@ -990,8 +467,8 @@ static inline __m128 dither4() { return _mm_mul_ps(d0, _mm_set1_ps(1/65536.0f)); } -// convert float to int16_t, interleave stereo -void AudioSRC::convertOutputToInt16(float** inputs, int16_t* output, int numFrames) { +// convert float to int16_t with dither, interleave stereo +void AudioSRC::convertOutput(float** inputs, int16_t* output, int numFrames) { __m128 scale = _mm_set1_ps(32768.0f); if (_numChannels == 1) { @@ -1062,6 +539,58 @@ void AudioSRC::convertOutputToInt16(float** inputs, int16_t* output, int numFram } } +// deinterleave stereo +void AudioSRC::convertInput(const float* input, float** outputs, int numFrames) { + + if (_numChannels == 1) { + + memcpy(outputs[0], input, numFrames * sizeof(float)); + + } else if (_numChannels == 2) { + + int i = 0; + for (; i < numFrames - 3; i += 4) { + __m128 f0 = _mm_loadu_ps(&input[2*i + 0]); + __m128 f1 = _mm_loadu_ps(&input[2*i + 4]); + + // deinterleave + _mm_storeu_ps(&outputs[0][i], _mm_shuffle_ps(f0, f1, _MM_SHUFFLE(2,0,2,0))); + _mm_storeu_ps(&outputs[1][i], _mm_shuffle_ps(f0, f1, _MM_SHUFFLE(3,1,3,1))); + } + for (; i < numFrames; i++) { + // deinterleave + outputs[0][i] = input[2*i + 0]; + outputs[1][i] = input[2*i + 1]; + } + } +} + +// interleave stereo +void AudioSRC::convertOutput(float** inputs, float* output, int numFrames) { + + if (_numChannels == 1) { + + memcpy(output, inputs[0], numFrames * sizeof(float)); + + } else if (_numChannels == 2) { + + int i = 0; + for (; i < numFrames - 3; i += 4) { + __m128 f0 = _mm_loadu_ps(&inputs[0][i]); + __m128 f1 = _mm_loadu_ps(&inputs[1][i]); + + // interleave + _mm_storeu_ps(&output[2*i + 0], _mm_unpacklo_ps(f0, f1)); + _mm_storeu_ps(&output[2*i + 4], _mm_unpackhi_ps(f0, f1)); + } + for (; i < numFrames; i++) { + // interleave + output[2*i + 0] = inputs[0][i]; + output[2*i + 1] = inputs[1][i]; + } + } +} + #else int AudioSRC::multirateFilter1(const float* input0, float* output0, int inputFrames) { @@ -1069,7 +598,7 @@ int AudioSRC::multirateFilter1(const float* input0, float* output0, int inputFra if (_step == 0) { // rational - int32_t i = hi32(_offset); + int32_t i = HI32(_offset); while (i < inputFrames) { @@ -1096,10 +625,10 @@ int AudioSRC::multirateFilter1(const float* input0, float* output0, int inputFra } else { // irrational - while (hi32(_offset) < inputFrames) { + while (HI32(_offset) < inputFrames) { - int32_t i = hi32(_offset); - uint32_t f = lo32(_offset); + int32_t i = HI32(_offset); + uint32_t f = LO32(_offset); uint32_t phase = f >> SRC_FRACBITS; float frac = (f & SRC_FRACMASK) * QFRAC_TO_FLOAT; @@ -1132,7 +661,7 @@ int AudioSRC::multirateFilter2(const float* input0, const float* input1, float* if (_step == 0) { // rational - int32_t i = hi32(_offset); + int32_t i = HI32(_offset); while (i < inputFrames) { @@ -1162,10 +691,10 @@ int AudioSRC::multirateFilter2(const float* input0, const float* input1, float* } else { // irrational - while (hi32(_offset) < inputFrames) { + while (HI32(_offset) < inputFrames) { - int32_t i = hi32(_offset); - uint32_t f = lo32(_offset); + int32_t i = HI32(_offset); + uint32_t f = LO32(_offset); uint32_t phase = f >> SRC_FRACBITS; float frac = (f & SRC_FRACMASK) * QFRAC_TO_FLOAT; @@ -1197,7 +726,7 @@ int AudioSRC::multirateFilter2(const float* input0, const float* input1, float* } // convert int16_t to float, deinterleave stereo -void AudioSRC::convertInputFromInt16(const int16_t* input, float** outputs, int numFrames) { +void AudioSRC::convertInput(const int16_t* input, float** outputs, int numFrames) { const float scale = 1/32768.0f; if (_numChannels == 1) { @@ -1221,8 +750,8 @@ static inline float dither() { return (int32_t)(r0 - r1) * (1/65536.0f); } -// convert float to int16_t, interleave stereo -void AudioSRC::convertOutputToInt16(float** inputs, int16_t* output, int numFrames) { +// convert float to int16_t with dither, interleave stereo +void AudioSRC::convertOutput(float** inputs, int16_t* output, int numFrames) { const float scale = 32768.0f; if (_numChannels == 1) { @@ -1261,9 +790,41 @@ void AudioSRC::convertOutputToInt16(float** inputs, int16_t* output, int numFram } } +// deinterleave stereo +void AudioSRC::convertInput(const float* input, float** outputs, int numFrames) { + + if (_numChannels == 1) { + + memcpy(outputs[0], input, numFrames * sizeof(float)); + + } else if (_numChannels == 2) { + for (int i = 0; i < numFrames; i++) { + // deinterleave + outputs[0][i] = input[2*i + 0]; + outputs[1][i] = input[2*i + 1]; + } + } +} + +// interleave stereo +void AudioSRC::convertOutput(float** inputs, float* output, int numFrames) { + + if (_numChannels == 1) { + + memcpy(output, inputs[0], numFrames * sizeof(float)); + + } else if (_numChannels == 2) { + for (int i = 0; i < numFrames; i++) { + // interleave + output[2*i + 0] = inputs[0][i]; + output[2*i + 1] = inputs[1][i]; + } + } +} + #endif -int AudioSRC::processFloat(float** inputs, float** outputs, int inputFrames) { +int AudioSRC::render(float** inputs, float** outputs, int inputFrames) { int outputFrames = 0; int nh = std::min(_numHistory, inputFrames); // number of frames from history buffer @@ -1272,19 +833,19 @@ int AudioSRC::processFloat(float** inputs, float** outputs, int inputFrames) { if (_numChannels == 1) { // refill history buffers - memcpy(_history[0] + _numHistory, _inputs[0], nh * sizeof(float)); + memcpy(_history[0] + _numHistory, inputs[0], nh * sizeof(float)); // process history buffer - outputFrames += multirateFilter1(_history[0], _outputs[0], nh); + outputFrames += multirateFilter1(_history[0], outputs[0], nh); // process remaining input if (ni) { - outputFrames += multirateFilter1(_inputs[0], _outputs[0] + outputFrames, ni); + outputFrames += multirateFilter1(inputs[0], outputs[0] + outputFrames, ni); } // shift history buffers if (ni) { - memcpy(_history[0], _inputs[0] + ni, _numHistory * sizeof(float)); + memcpy(_history[0], inputs[0] + ni, _numHistory * sizeof(float)); } else { memmove(_history[0], _history[0] + nh, _numHistory * sizeof(float)); } @@ -1292,15 +853,15 @@ int AudioSRC::processFloat(float** inputs, float** outputs, int inputFrames) { } else if (_numChannels == 2) { // refill history buffers - memcpy(_history[0] + _numHistory, _inputs[0], nh * sizeof(float)); - memcpy(_history[1] + _numHistory, _inputs[1], nh * sizeof(float)); + memcpy(_history[0] + _numHistory, inputs[0], nh * sizeof(float)); + memcpy(_history[1] + _numHistory, inputs[1], nh * sizeof(float)); // process history buffer - outputFrames += multirateFilter2(_history[0], _history[1], _outputs[0], _outputs[1], nh); + outputFrames += multirateFilter2(_history[0], _history[1], outputs[0], outputs[1], nh); // process remaining input if (ni) { - outputFrames += multirateFilter2(_inputs[0], _inputs[1], _outputs[0] + outputFrames, _outputs[1] + outputFrames, ni); + outputFrames += multirateFilter2(inputs[0], inputs[1], outputs[0] + outputFrames, outputs[1] + outputFrames, ni); } // shift history buffers @@ -1320,7 +881,7 @@ AudioSRC::AudioSRC(int inputSampleRate, int outputSampleRate, int numChannels) { assert(inputSampleRate > 0); assert(outputSampleRate > 0); assert(numChannels > 0); - assert(numChannels <= MAX_CHANNELS); + assert(numChannels <= SRC_MAX_CHANNELS); _inputSampleRate = inputSampleRate; _outputSampleRate = outputSampleRate; @@ -1349,7 +910,7 @@ AudioSRC::AudioSRC(int inputSampleRate, int outputSampleRate, int numChannels) { _numTaps = createIrrationalFilter(_upFactor, _downFactor, 1.0f); } - //printf("up=%d down=%.3f taps=%d\n", _upFactor, _downFactor + (lo32(_step)< +static const int SRC_MAX_CHANNELS = 2; + +// polyphase filter +static const int SRC_PHASEBITS = 8; +static const int SRC_PHASES = (1 << SRC_PHASEBITS); +static const int SRC_FRACBITS = 32 - SRC_PHASEBITS; +static const uint32_t SRC_FRACMASK = (1 << SRC_FRACBITS) - 1; + +static const float QFRAC_TO_FLOAT = 1.0f / (1 << SRC_FRACBITS); +static const float Q32_TO_FLOAT = 1.0f / (1ULL << 32); + +// blocking size in frames, chosen so block processing fits in L1 cache +static const int SRC_BLOCK = 256; + class AudioSRC { public: - static const int MAX_CHANNELS = 2; - AudioSRC(int inputSampleRate, int outputSampleRate, int numChannels); ~AudioSRC(); + // deinterleaved float input/output (native format) + int render(float** inputs, float** outputs, int inputFrames); + + // interleaved int16_t input/output int render(const int16_t* input, int16_t* output, int inputFrames); + // interleaved float input/output + int render(const float* input, float* output, int inputFrames); + int getMinOutput(int inputFrames); int getMaxOutput(int inputFrames); int getMinInput(int outputFrames); @@ -33,9 +52,9 @@ private: float* _polyphaseFilter; int* _stepTable; - float* _history[MAX_CHANNELS]; - float* _inputs[MAX_CHANNELS]; - float* _outputs[MAX_CHANNELS]; + float* _history[SRC_MAX_CHANNELS]; + float* _inputs[SRC_MAX_CHANNELS]; + float* _outputs[SRC_MAX_CHANNELS]; int _inputSampleRate; int _outputSampleRate; @@ -57,10 +76,17 @@ private: int multirateFilter1(const float* input0, float* output0, int inputFrames); int multirateFilter2(const float* input0, const float* input1, float* output0, float* output1, int inputFrames); - void convertInputFromInt16(const int16_t* input, float** outputs, int numFrames); - void convertOutputToInt16(float** inputs, int16_t* output, int numFrames); + int multirateFilter1_SSE(const float* input0, float* output0, int inputFrames); + int multirateFilter2_SSE(const float* input0, const float* input1, float* output0, float* output1, int inputFrames); - int processFloat(float** inputs, float** outputs, int inputFrames); + int multirateFilter1_AVX2(const float* input0, float* output0, int inputFrames); + int multirateFilter2_AVX2(const float* input0, const float* input1, float* output0, float* output1, int inputFrames); + + void convertInput(const int16_t* input, float** outputs, int numFrames); + void convertOutput(float** inputs, int16_t* output, int numFrames); + + void convertInput(const float* input, float** outputs, int numFrames); + void convertOutput(float** inputs, float* output, int numFrames); }; #endif // AudioSRC_h diff --git a/libraries/audio/src/AudioSRCData.h b/libraries/audio/src/AudioSRCData.h new file mode 100644 index 0000000000..8ab868e898 --- /dev/null +++ b/libraries/audio/src/AudioSRCData.h @@ -0,0 +1,548 @@ +// +// AudioSRCData.h +// libraries/audio/src +// +// Created by Ken Cooke on 6/6/16. +// Copyright 2016 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 +// + +// +// Prototype filter coefficients for audio sampling rate conversion. +// +// See "Design of Optimal Minimum Phase Digital FIR Filters Using Discrete Hilbert Transforms" +// IEEE TRANSACTIONS ON SIGNAL PROCESSING, May 2000 +// +// Minimum-phase equiripple FIR lowpass +// taps = 96, phases = 32 +// +// passband = 0.918 (20.2khz @ 44.1khz sampling rate) +// stopband = 1.010 (22.2khz @ 44.1khz sampling rate) +// passband ripple = +-0.01dB +// stopband attn = -125dB (-70dB at 1.000) +// +// Resampling algorithm: +// One of two methods is used, depending on whether the conversion is reducible to a ratio of small integers L/M. +// For rational ratio, the prototype is upsampled to L/M polyphase filter using 3rd-order Lagrange interpolation. +// For irrational ratio, the prototype is upsampled to 256/M polyphase filter, followed by linear interpolation. +// For both cases, oversampling at each stage ensures the original passband and stopband specifications are met. +// +static const int PROTOTYPE_TAPS = 96; // filter taps per phase +static const int PROTOTYPE_PHASES = 32; // oversampling factor + +static const float prototypeFilter[PROTOTYPE_TAPS * PROTOTYPE_PHASES] = { + 0.00000000e+00f, 1.55021703e-05f, 1.46054865e-05f, 2.07057160e-05f, 2.91335519e-05f, 4.00091078e-05f, + 5.33544450e-05f, 7.03618468e-05f, 9.10821639e-05f, 1.16484613e-04f, 1.47165999e-04f, 1.84168304e-04f, + 2.28429617e-04f, 2.80913884e-04f, 3.42940399e-04f, 4.15773039e-04f, 5.01023255e-04f, 6.00234953e-04f, + 7.15133271e-04f, 8.47838855e-04f, 1.00032516e-03f, 1.17508881e-03f, 1.37452550e-03f, 1.60147614e-03f, + 1.85886458e-03f, 2.14985024e-03f, 2.47783071e-03f, 2.84666764e-03f, 3.26016878e-03f, 3.72252797e-03f, + 4.23825900e-03f, 4.81207874e-03f, 5.44904143e-03f, 6.15447208e-03f, 6.93399929e-03f, 7.79337059e-03f, + 8.73903392e-03f, 9.77729117e-03f, 1.09149561e-02f, 1.21591316e-02f, 1.35171164e-02f, 1.49965439e-02f, + 1.66053136e-02f, 1.83515384e-02f, 2.02435362e-02f, 2.22899141e-02f, 2.44995340e-02f, 2.68813362e-02f, + 2.94443254e-02f, 3.21979928e-02f, 3.51514690e-02f, 3.83143719e-02f, 4.16960560e-02f, 4.53060504e-02f, + 4.91538115e-02f, 5.32486197e-02f, 5.75998650e-02f, 6.22164253e-02f, 6.71072811e-02f, 7.22809789e-02f, + 7.77457552e-02f, 8.35095233e-02f, 8.95796944e-02f, 9.59631768e-02f, 1.02666457e-01f, 1.09695215e-01f, + 1.17054591e-01f, 1.24748885e-01f, 1.32781656e-01f, 1.41155521e-01f, 1.49872243e-01f, 1.58932534e-01f, + 1.68335961e-01f, 1.78081143e-01f, 1.88165339e-01f, 1.98584621e-01f, 2.09333789e-01f, 2.20406193e-01f, + 2.31793899e-01f, 2.43487398e-01f, 2.55475740e-01f, 2.67746404e-01f, 2.80285305e-01f, 2.93076743e-01f, + 3.06103423e-01f, 3.19346351e-01f, 3.32784916e-01f, 3.46396772e-01f, 3.60158039e-01f, 3.74043042e-01f, + 3.88024564e-01f, 4.02073759e-01f, 4.16160177e-01f, 4.30251886e-01f, 4.44315429e-01f, 4.58315954e-01f, + 4.72217175e-01f, 4.85981675e-01f, 4.99570709e-01f, 5.12944586e-01f, 5.26062401e-01f, 5.38882630e-01f, + 5.51362766e-01f, 5.63459860e-01f, 5.75130384e-01f, 5.86330458e-01f, 5.97016050e-01f, 6.07143161e-01f, + 6.16667840e-01f, 6.25546499e-01f, 6.33735979e-01f, 6.41193959e-01f, 6.47878856e-01f, 6.53750084e-01f, + 6.58768549e-01f, 6.62896349e-01f, 6.66097381e-01f, 6.68337353e-01f, 6.69583869e-01f, 6.69807061e-01f, + 6.68979117e-01f, 6.67075139e-01f, 6.64072812e-01f, 6.59952827e-01f, 6.54699116e-01f, 6.48298688e-01f, + 6.40742160e-01f, 6.32023668e-01f, 6.22141039e-01f, 6.11095903e-01f, 5.98893921e-01f, 5.85544600e-01f, + 5.71061707e-01f, 5.55463040e-01f, 5.38770639e-01f, 5.21010762e-01f, 5.02213839e-01f, 4.82414572e-01f, + 4.61651859e-01f, 4.39968628e-01f, 4.17412000e-01f, 3.94032951e-01f, 3.69886464e-01f, 3.45031084e-01f, + 3.19529091e-01f, 2.93446187e-01f, 2.66851164e-01f, 2.39815999e-01f, 2.12415399e-01f, 1.84726660e-01f, + 1.56829293e-01f, 1.28804933e-01f, 1.00736965e-01f, 7.27100355e-02f, 4.48100810e-02f, 1.71237415e-02f, + -1.02620228e-02f, -3.72599591e-02f, -6.37832871e-02f, -8.97457733e-02f, -1.15062201e-01f, -1.39648782e-01f, + -1.63423488e-01f, -1.86306368e-01f, -2.08220103e-01f, -2.29090072e-01f, -2.48845046e-01f, -2.67417270e-01f, + -2.84742946e-01f, -3.00762597e-01f, -3.15421127e-01f, -3.28668542e-01f, -3.40459849e-01f, -3.50755400e-01f, + -3.59521402e-01f, -3.66729768e-01f, -3.72358475e-01f, -3.76391839e-01f, -3.78820421e-01f, -3.79641287e-01f, + -3.78858203e-01f, -3.76481336e-01f, -3.72527677e-01f, -3.67020780e-01f, -3.59990760e-01f, -3.51474372e-01f, + -3.41514630e-01f, -3.30160971e-01f, -3.17468898e-01f, -3.03499788e-01f, -2.88320749e-01f, -2.72004315e-01f, + -2.54628056e-01f, -2.36274454e-01f, -2.17030464e-01f, -1.96986952e-01f, -1.76238733e-01f, -1.54883647e-01f, + -1.33022496e-01f, -1.10758449e-01f, -8.81964466e-02f, -6.54430504e-02f, -4.26055475e-02f, -1.97916415e-02f, + 2.89108184e-03f, 2.53355868e-02f, 4.74362201e-02f, 6.90887518e-02f, 9.01914308e-02f, 1.10644978e-01f, + 1.30353494e-01f, 1.49224772e-01f, 1.67170735e-01f, 1.84107975e-01f, 1.99958067e-01f, 2.14648181e-01f, + 2.28111323e-01f, 2.40286622e-01f, 2.51119890e-01f, 2.60563701e-01f, 2.68577740e-01f, 2.75129027e-01f, + 2.80192144e-01f, 2.83749177e-01f, 2.85790223e-01f, 2.86312986e-01f, 2.85323221e-01f, 2.82834421e-01f, + 2.78867915e-01f, 2.73452721e-01f, 2.66625431e-01f, 2.58429983e-01f, 2.48917457e-01f, 2.38145826e-01f, + 2.26179680e-01f, 2.13089734e-01f, 1.98952740e-01f, 1.83850758e-01f, 1.67870897e-01f, 1.51104879e-01f, + 1.33648388e-01f, 1.15600665e-01f, 9.70639763e-02f, 7.81429119e-02f, 5.89439889e-02f, 3.95749746e-02f, + 2.01442353e-02f, 7.60241152e-04f, -1.84690990e-02f, -3.74370397e-02f, -5.60385970e-02f, -7.41711039e-02f, + -9.17348686e-02f, -1.08633632e-01f, -1.24775254e-01f, -1.40071993e-01f, -1.54441372e-01f, -1.67806284e-01f, + -1.80095654e-01f, -1.91244732e-01f, -2.01195605e-01f, -2.09897310e-01f, -2.17306320e-01f, -2.23386736e-01f, + -2.28110407e-01f, -2.31457193e-01f, -2.33415044e-01f, -2.33980051e-01f, -2.33156463e-01f, -2.30956673e-01f, + -2.27401097e-01f, -2.22518148e-01f, -2.16343899e-01f, -2.08921985e-01f, -2.00303365e-01f, -1.90545790e-01f, + -1.79713804e-01f, -1.67877977e-01f, -1.55114789e-01f, -1.41505907e-01f, -1.27137921e-01f, -1.12101628e-01f, + -9.64915640e-02f, -8.04054232e-02f, -6.39434707e-02f, -4.72078814e-02f, -3.03021635e-02f, -1.33305082e-02f, + 3.60284977e-03f, 2.03942507e-02f, 3.69413014e-02f, 5.31433810e-02f, 6.89024656e-02f, 8.41234679e-02f, + 9.87150268e-02f, 1.12589969e-01f, 1.25665865e-01f, 1.37865538e-01f, 1.49117506e-01f, 1.59356490e-01f, + 1.68523664e-01f, 1.76567229e-01f, 1.83442499e-01f, 1.89112308e-01f, 1.93547212e-01f, 1.96725586e-01f, + 1.98633878e-01f, 1.99266486e-01f, 1.98625999e-01f, 1.96723008e-01f, 1.93576075e-01f, 1.89211557e-01f, + 1.83663562e-01f, 1.76973516e-01f, 1.69190033e-01f, 1.60368490e-01f, 1.50570805e-01f, 1.39864815e-01f, + 1.28324021e-01f, 1.16026978e-01f, 1.03056879e-01f, 8.95008829e-02f, 7.54496798e-02f, 6.09968238e-02f, + 4.62380664e-02f, 3.12708901e-02f, 1.61936956e-02f, 1.10531988e-03f, -1.38957653e-02f, -2.87119784e-02f, + -4.32472742e-02f, -5.74078385e-02f, -7.11026311e-02f, -8.42439713e-02f, -9.67481917e-02f, -1.08536049e-01f, + -1.19533350e-01f, -1.29671345e-01f, -1.38887238e-01f, -1.47124498e-01f, -1.54333373e-01f, -1.60470968e-01f, + -1.65501755e-01f, -1.69397631e-01f, -1.72138140e-01f, -1.73710602e-01f, -1.74110159e-01f, -1.73339798e-01f, + -1.71410274e-01f, -1.68340111e-01f, -1.64155335e-01f, -1.58889414e-01f, -1.52582850e-01f, -1.45283122e-01f, + -1.37044042e-01f, -1.27925722e-01f, -1.17993860e-01f, -1.07319421e-01f, -9.59781808e-02f, -8.40500777e-02f, + -7.16188049e-02f, -5.87710561e-02f, -4.55961475e-02f, -3.21851919e-02f, -1.86306406e-02f, -5.02554942e-03f, + 8.53698384e-03f, 2.19645467e-02f, 3.51659468e-02f, 4.80518693e-02f, 6.05355056e-02f, 7.25330700e-02f, + 8.39645094e-02f, 9.47537898e-02f, 1.04829753e-01f, 1.14126254e-01f, 1.22582788e-01f, 1.30144907e-01f, + 1.36764459e-01f, 1.42400029e-01f, 1.47017076e-01f, 1.50588312e-01f, 1.53093700e-01f, 1.54520736e-01f, + 1.54864367e-01f, 1.54127119e-01f, 1.52318991e-01f, 1.49457408e-01f, 1.45567062e-01f, 1.40679709e-01f, + 1.34833933e-01f, 1.28074855e-01f, 1.20453893e-01f, 1.12028129e-01f, 1.02860307e-01f, 9.30178765e-02f, + 8.25730032e-02f, 7.16016450e-02f, 6.01833134e-02f, 4.84002546e-02f, 3.63370724e-02f, 2.40800037e-02f, + 1.17163168e-02f, -6.66217400e-04f, -1.29801121e-02f, -2.51385315e-02f, -3.70562030e-02f, -4.86497748e-02f, + -5.98384928e-02f, -7.05447859e-02f, -8.06947592e-02f, -9.02187441e-02f, -9.90517313e-02f, -1.07133911e-01f, + -1.14410951e-01f, -1.20834483e-01f, -1.26362422e-01f, -1.30959116e-01f, -1.34595787e-01f, -1.37250547e-01f, + -1.38908600e-01f, -1.39562374e-01f, -1.39211442e-01f, -1.37862602e-01f, -1.35529795e-01f, -1.32233909e-01f, + -1.28002721e-01f, -1.22870611e-01f, -1.16878278e-01f, -1.10072477e-01f, -1.02505698e-01f, -9.42356124e-02f, + -8.53248753e-02f, -7.58404912e-02f, -6.58532924e-02f, -5.54376360e-02f, -4.46705953e-02f, -3.36315414e-02f, + -2.24015972e-02f, -1.10628991e-02f, 3.01894735e-04f, 1.16101918e-02f, 2.27801642e-02f, 3.37311642e-02f, + 4.43845430e-02f, 5.46640016e-02f, 6.44962637e-02f, 7.38115400e-02f, 8.25440784e-02f, 9.06325572e-02f, + 9.80206066e-02f, 1.04657146e-01f, 1.10496723e-01f, 1.15499920e-01f, 1.19633523e-01f, 1.22870824e-01f, + 1.25191729e-01f, 1.26582959e-01f, 1.27038061e-01f, 1.26557494e-01f, 1.25148528e-01f, 1.22825305e-01f, + 1.19608512e-01f, 1.15525479e-01f, 1.10609643e-01f, 1.04900592e-01f, 9.84435537e-02f, 9.12890948e-02f, + 8.34927732e-02f, 7.51146973e-02f, 6.62190194e-02f, 5.68735547e-02f, 4.71491262e-02f, 3.71191855e-02f, + 2.68591932e-02f, 1.64459573e-02f, 5.95731808e-03f, -4.52874940e-03f, -1.49344723e-02f, -2.51829130e-02f, + -3.51986373e-02f, -4.49081427e-02f, -5.42404654e-02f, -6.31276969e-02f, -7.15054163e-02f, -7.93132713e-02f, + -8.64953327e-02f, -9.30005042e-02f, -9.87829011e-02f, -1.03802223e-01f, -1.08023943e-01f, -1.11419636e-01f, + -1.13967111e-01f, -1.15650603e-01f, -1.16460855e-01f, -1.16395152e-01f, -1.15457368e-01f, -1.13657871e-01f, + -1.11013433e-01f, -1.07547117e-01f, -1.03288073e-01f, -9.82712708e-02f, -9.25372646e-02f, -8.61318657e-02f, + -7.91057486e-02f, -7.15141053e-02f, -6.34161588e-02f, -5.48747791e-02f, -4.59559696e-02f, -3.67282941e-02f, + -2.72624874e-02f, -1.76307914e-02f, -7.90648674e-03f, 1.83670340e-03f, 1.15251424e-02f, 2.10858716e-02f, + 3.04471304e-02f, 3.95388944e-02f, 4.82933904e-02f, 5.66456655e-02f, 6.45340054e-02f, 7.19003487e-02f, + 7.86908695e-02f, 8.48562395e-02f, 9.03519908e-02f, 9.51389501e-02f, 9.91834077e-02f, 1.02457361e-01f, + 1.04938834e-01f, 1.06611872e-01f, 1.07466724e-01f, 1.07499917e-01f, 1.06714213e-01f, 1.05118588e-01f, + 1.02728167e-01f, 9.95640680e-02f, 9.56532488e-02f, 9.10282406e-02f, 8.57269309e-02f, 7.97922261e-02f, + 7.32717395e-02f, 6.62174249e-02f, 5.86850536e-02f, 5.07339959e-02f, 4.24265058e-02f, 3.38274345e-02f, + 2.50036502e-02f, 1.60234844e-02f, 6.95628026e-03f, -2.12820655e-03f, -1.11602438e-02f, -2.00708281e-02f, + -2.87920337e-02f, -3.72576320e-02f, -4.54035426e-02f, -5.31684173e-02f, -6.04938939e-02f, -6.73253212e-02f, + -7.36119310e-02f, -7.93072981e-02f, -8.43697556e-02f, -8.87625537e-02f, -9.24542939e-02f, -9.54189981e-02f, + -9.76364402e-02f, -9.90921435e-02f, -9.97776003e-02f, -9.96902366e-02f, -9.88334463e-02f, -9.72165780e-02f, + -9.48547668e-02f, -9.17688999e-02f, -8.79853312e-02f, -8.35357688e-02f, -7.84569594e-02f, -7.27903677e-02f, + -6.65818940e-02f, -5.98814932e-02f, -5.27427333e-02f, -4.52224733e-02f, -3.73802459e-02f, -2.92780037e-02f, + -2.09794209e-02f, -1.25495498e-02f, -4.05425988e-03f, 4.44034349e-03f, 1.28682571e-02f, 2.11643361e-02f, + 2.92645357e-02f, 3.71066200e-02f, 4.46305203e-02f, 5.17788267e-02f, 5.84972389e-02f, 6.47349496e-02f, + 7.04450836e-02f, 7.55849928e-02f, 8.01165748e-02f, 8.40066506e-02f, 8.72270848e-02f, 8.97550618e-02f, + 9.15732179e-02f, 9.26698315e-02f, 9.30387881e-02f, 9.26796720e-02f, 9.15978025e-02f, 8.98040443e-02f, + 8.73148489e-02f, 8.41520461e-02f, 8.03426093e-02f, 7.59185468e-02f, 7.09165136e-02f, 6.53776255e-02f, + 5.93470480e-02f, 5.28736293e-02f, 4.60095655e-02f, 3.88099545e-02f, 3.13323302e-02f, 2.36362162e-02f, + 1.57827398e-02f, 7.83395091e-03f, -1.47413782e-04f, -8.09864153e-03f, -1.59574406e-02f, -2.36623595e-02f, + -3.11534717e-02f, -3.83725840e-02f, -4.52638947e-02f, -5.17743411e-02f, -5.78539729e-02f, -6.34564348e-02f, + -6.85392092e-02f, -7.30640654e-02f, -7.69971954e-02f, -8.03096220e-02f, -8.29772975e-02f, -8.49813524e-02f, + -8.63081836e-02f, -8.69495746e-02f, -8.69027157e-02f, -8.61702687e-02f, -8.47602668e-02f, -8.26860569e-02f, + -7.99661981e-02f, -7.66242997e-02f, -7.26887788e-02f, -6.81926752e-02f, -6.31733712e-02f, -5.76722279e-02f, + -5.17343061e-02f, -4.54080069e-02f, -3.87446321e-02f, -3.17980032e-02f, -2.46239897e-02f, -1.72801497e-02f, + -9.82518156e-03f, -2.31845300e-03f, 5.18037510e-03f, 1.26119044e-02f, 1.99174857e-02f, 2.70395921e-02f, + 3.39223499e-02f, 4.05119404e-02f, 4.67570465e-02f, 5.26092142e-02f, 5.80232695e-02f, 6.29576539e-02f, + 6.73747113e-02f, 7.12410320e-02f, 7.45276905e-02f, 7.72104218e-02f, 7.92698394e-02f, 8.06915952e-02f, + 8.14664004e-02f, 8.15901977e-02f, 8.10640907e-02f, 7.98943315e-02f, 7.80922975e-02f, 7.56743792e-02f, + 7.26617861e-02f, 6.90804346e-02f, 6.49606433e-02f, 6.03370049e-02f, 5.52479503e-02f, 4.97355660e-02f, + 4.38451300e-02f, 3.76248662e-02f, 3.11254263e-02f, 2.43995757e-02f, 1.75017105e-02f, 1.04874823e-02f, + 3.41321948e-03f, -3.66433362e-03f, -1.06886566e-02f, -1.76037566e-02f, -2.43547422e-02f, -3.08881238e-02f, + -3.71523818e-02f, -4.30982377e-02f, -4.86791529e-02f, -5.38515978e-02f, -5.85754991e-02f, -6.28144137e-02f, + -6.65359631e-02f, -6.97119559e-02f, -7.23186409e-02f, -7.43369897e-02f, -7.57526047e-02f, -7.65560812e-02f, + -7.67428560e-02f, -7.63134051e-02f, -7.52730583e-02f, -7.36321241e-02f, -7.14055927e-02f, -6.86132027e-02f, + -6.52791213e-02f, -6.14318004e-02f, -5.71037475e-02f, -5.23312158e-02f, -4.71539306e-02f, -4.16147519e-02f, + -3.57593331e-02f, -2.96357023e-02f, -2.32939478e-02f, -1.67857228e-02f, -1.01639251e-02f, -3.48213128e-03f, + 3.20566951e-03f, 9.84566549e-03f, 1.63845318e-02f, 2.27699627e-02f, 2.89509937e-02f, 3.48784838e-02f, + 4.05054571e-02f, 4.57875191e-02f, 5.06831561e-02f, 5.51541055e-02f, 5.91656321e-02f, 6.26867948e-02f, + 6.56907214e-02f, 6.81547545e-02f, 7.00607045e-02f, 7.13948753e-02f, 7.21482790e-02f, 7.23165894e-02f, + 7.19002973e-02f, 7.09044846e-02f, 6.93390331e-02f, 6.72183039e-02f, 6.45611568e-02f, 6.13906537e-02f, + 5.77340810e-02f, 5.36223917e-02f, 4.90902973e-02f, 4.41756853e-02f, 3.89195025e-02f, 3.33653266e-02f, + 2.75589553e-02f, 2.15482187e-02f, 1.53823433e-02f, 9.11173206e-03f, 2.78750380e-03f, -3.53899736e-03f, + -9.81648845e-03f, -1.59942887e-02f, -2.20226002e-02f, -2.78530676e-02f, -3.34389835e-02f, -3.87358558e-02f, + -4.37015752e-02f, -4.82968641e-02f, -5.24856104e-02f, -5.62350079e-02f, -5.95160314e-02f, -6.23034090e-02f, + -6.45760369e-02f, -6.63170246e-02f, -6.75138263e-02f, -6.81583864e-02f, -6.82471093e-02f, -6.77809819e-02f, + -6.67654439e-02f, -6.52104027e-02f, -6.31301405e-02f, -6.05431381e-02f, -5.74719510e-02f, -5.39430121e-02f, + -4.99864152e-02f, -4.56356108e-02f, -4.09271785e-02f, -3.59005358e-02f, -3.05975021e-02f, -2.50620982e-02f, + -1.93400931e-02f, -1.34786109e-02f, -7.52582921e-03f, -1.53047296e-03f, 4.45846396e-03f, 1.03922252e-02f, + 1.62226043e-02f, 2.19024111e-02f, 2.73857927e-02f, 3.26286453e-02f, 3.75889120e-02f, 4.22270162e-02f, + 4.65060678e-02f, 5.03922602e-02f, 5.38550360e-02f, 5.68673912e-02f, 5.94061299e-02f, 6.14518959e-02f, + 6.29894927e-02f, 6.40078422e-02f, 6.45002081e-02f, 6.44641312e-02f, 6.39014463e-02f, 6.28183549e-02f, + 6.12252434e-02f, 5.91366226e-02f, 5.65710713e-02f, 5.35509478e-02f, 5.01023211e-02f, 4.62546289e-02f, + 4.20405644e-02f, 3.74956324e-02f, 3.26580309e-02f, 2.75681921e-02f, 2.22685138e-02f, 1.68029869e-02f, + 1.12168479e-02f, 5.55616360e-03f, -1.32475496e-04f, -5.80242145e-03f, -1.14072870e-02f, -1.69013632e-02f, + -2.22399629e-02f, -2.73798231e-02f, -3.22793559e-02f, -3.68992177e-02f, -4.12022700e-02f, -4.51542301e-02f, + -4.87237130e-02f, -5.18825743e-02f, -5.46061242e-02f, -5.68733215e-02f, -5.86668721e-02f, -5.99735198e-02f, + -6.07838952e-02f, -6.10928895e-02f, -6.08993923e-02f, -6.02064781e-02f, -5.90213291e-02f, -5.73550887e-02f, + -5.52228853e-02f, -5.26435817e-02f, -4.96396897e-02f, -4.62371294e-02f, -4.24650256e-02f, -3.83554628e-02f, + -3.39432096e-02f, -2.92654225e-02f, -2.43613233e-02f, -1.92718970e-02f, -1.40395616e-02f, -8.70771728e-03f, + -3.32056777e-03f, 2.07744785e-03f, 7.44190391e-03f, 1.27287222e-02f, 1.78946228e-02f, 2.28975002e-02f, + 2.76965843e-02f, 3.22530140e-02f, 3.65299534e-02f, 4.04930363e-02f, 4.41105069e-02f, 4.73536159e-02f, + 5.01967201e-02f, 5.26175750e-02f, 5.45974724e-02f, 5.61213729e-02f, 5.71780843e-02f, 5.77601946e-02f, + 5.78643759e-02f, 5.74910914e-02f, 5.66448597e-02f, 5.53340158e-02f, 5.35707338e-02f, 5.13708843e-02f, + 4.87538683e-02f, 4.57425137e-02f, 4.23627999e-02f, 3.86437075e-02f, 3.46169024e-02f, 3.03165387e-02f, + 2.57788894e-02f, 2.10421222e-02f, 1.61459251e-02f, 1.11311994e-02f, 6.03970466e-03f, 9.13695817e-04f, + -4.20433431e-03f, -9.27218149e-03f, -1.42480682e-02f, -1.90911878e-02f, -2.37618648e-02f, -2.82220093e-02f, + -3.24353766e-02f, -3.63678336e-02f, -3.99876924e-02f, -4.32659237e-02f, -4.61764207e-02f, -4.86961602e-02f, + -5.08054551e-02f, -5.24880386e-02f, -5.37312181e-02f, -5.45260166e-02f, -5.48671104e-02f, -5.47530531e-02f, + -5.41860463e-02f, -5.31721475e-02f, -5.17210363e-02f, -4.98459868e-02f, -4.75637647e-02f, -4.48944406e-02f, + -4.18612746e-02f, -3.84904206e-02f, -3.48107925e-02f, -3.08537797e-02f, -2.66529685e-02f, -2.22438695e-02f, + -1.76636682e-02f, -1.29507560e-02f, -8.14466071e-03f, -3.28544776e-03f, 1.58643018e-03f, 6.43050440e-03f, + 1.12067405e-02f, 1.58756642e-02f, 2.03989020e-02f, 2.47393345e-02f, 2.88614617e-02f, 3.27317634e-02f, + 3.63187992e-02f, 3.95936470e-02f, 4.25300387e-02f, 4.51045672e-02f, 4.72968940e-02f, 4.90899703e-02f, + 5.04700047e-02f, 5.14267809e-02f, 5.19535643e-02f, 5.20472034e-02f, 5.17082287e-02f, 5.09406434e-02f, + 4.97521048e-02f, 4.81537188e-02f, 4.61599131e-02f, 4.37884262e-02f, 4.10600706e-02f, 3.79985488e-02f, + 3.46302622e-02f, 3.09841217e-02f, 2.70912412e-02f, 2.29847199e-02f, 1.86992847e-02f, 1.42711599e-02f, + 9.73752669e-03f, 5.13643650e-03f, 5.06379454e-04f, -4.11408166e-03f, -8.68649476e-03f, -1.31729621e-02f, + -1.75363807e-02f, -2.17408089e-02f, -2.57516979e-02f, -2.95362143e-02f, -3.30635093e-02f, -3.63049622e-02f, + -3.92344048e-02f, -4.18283298e-02f, -4.40661418e-02f, -4.59301913e-02f, -4.74060505e-02f, -4.84825511e-02f, + -4.91518827e-02f, -4.94096235e-02f, -4.92548579e-02f, -4.86900251e-02f, -4.77210458e-02f, -4.63571741e-02f, + -4.46108878e-02f, -4.24979107e-02f, -4.00368564e-02f, -3.72492987e-02f, -3.41594108e-02f, -3.07938448e-02f, + -2.71814552e-02f, -2.33531198e-02f, -1.93413598e-02f, -1.51802063e-02f, -1.09048013e-02f, -6.55114338e-03f, + -2.15581014e-03f, 2.24443555e-03f, 6.61280814e-03f, 1.09129453e-02f, 1.51091980e-02f, 1.91667630e-02f, + 2.30522168e-02f, 2.67335907e-02f, 3.01807365e-02f, 3.33655579e-02f, 3.62622051e-02f, 3.88473226e-02f, + 4.11002204e-02f, 4.30030300e-02f, 4.45408790e-02f, 4.57019705e-02f, 4.64777109e-02f, 4.68627135e-02f, + 4.68549093e-02f, 4.64554958e-02f, 4.56689373e-02f, 4.45029599e-02f, 4.29683919e-02f, 4.10791386e-02f, + 3.88520159e-02f, 3.63066475e-02f, 3.34652385e-02f, 3.03523892e-02f, 2.69949681e-02f, 2.34217263e-02f, + 1.96632025e-02f, 1.57513974e-02f, 1.17194459e-02f, 7.60145677e-03f, 3.43215481e-03f, -7.53454950e-04f, + -4.92025229e-03f, -9.03345904e-03f, -1.30587503e-02f, -1.69627406e-02f, -2.07130441e-02f, -2.42787472e-02f, + -2.76304969e-02f, -3.07408842e-02f, -3.35845310e-02f, -3.61384026e-02f, -3.83819804e-02f, -4.02973364e-02f, + -4.18693911e-02f, -4.30859849e-02f, -4.39379525e-02f, -4.44192202e-02f, -4.45268207e-02f, -4.42609489e-02f, + -4.36249417e-02f, -4.26251693e-02f, -4.12710965e-02f, -3.95751119e-02f, -3.75524034e-02f, -3.52209020e-02f, + -3.26010732e-02f, -2.97156826e-02f, -2.65897306e-02f, -2.32501339e-02f, -1.97255230e-02f, -1.60459906e-02f, + -1.22428645e-02f, -8.34840613e-03f, -4.39555788e-03f, -4.17641093e-04f, 3.55186529e-03f, 7.47969548e-03f, + 1.13330289e-02f, 1.50796895e-02f, 1.86886063e-02f, 2.21298440e-02f, 2.53750227e-02f, 2.83974776e-02f, + 3.11724713e-02f, 3.36774564e-02f, 3.58921485e-02f, 3.77988281e-02f, 3.93823848e-02f, 4.06304645e-02f, + 4.15335460e-02f, 4.20850895e-02f, 4.22814530e-02f, 4.21220657e-02f, 4.16092724e-02f, 4.07484568e-02f, + 3.95478256e-02f, 3.80185099e-02f, 3.61742882e-02f, 3.40316228e-02f, 3.16093467e-02f, 2.89286854e-02f, + 2.60129143e-02f, 2.28872072e-02f, 1.95785162e-02f, 1.61151429e-02f, 1.25266872e-02f, 8.84367289e-03f, + 5.09737541e-03f, 1.31946573e-03f, -2.45819207e-03f, -6.20382907e-03f, -9.88599514e-03f, -1.34739714e-02f, + -1.69377975e-02f, -2.02487225e-02f, -2.33793144e-02f, -2.63038233e-02f, -2.89981802e-02f, -3.14404213e-02f, + -3.36107546e-02f, -3.54916723e-02f, -3.70682427e-02f, -3.83280672e-02f, -3.92614736e-02f, -3.98615776e-02f, + -4.01243243e-02f, -4.00484517e-02f, -3.96356708e-02f, -3.88903731e-02f, -3.78198781e-02f, -3.64341365e-02f, + -3.47457457e-02f, -3.27698392e-02f, -3.05238882e-02f, -2.80276282e-02f, -2.53028218e-02f, -2.23730957e-02f, + -1.92637467e-02f, -1.60015029e-02f, -1.26142882e-02f, -9.13104283e-03f, -5.58138981e-03f, -1.99542434e-03f, + 1.59649307e-03f, 5.16408174e-03f, 8.67737144e-03f, 1.21068581e-02f, 1.54239205e-02f, 1.86009100e-02f, + 2.16114772e-02f, 2.44306994e-02f, 2.70354163e-02f, 2.94042665e-02f, 3.15179985e-02f, 3.33595356e-02f, + 3.49141593e-02f, 3.61696229e-02f, 3.71161871e-02f, 3.77468512e-02f, 3.80571878e-02f, 3.80455485e-02f, + 3.77129900e-02f, 3.70632810e-02f, 3.61028508e-02f, 3.48407199e-02f, 3.32884428e-02f, 3.14600053e-02f, + 2.93716228e-02f, 2.70417408e-02f, 2.44907277e-02f, 2.17407576e-02f, 1.88156734e-02f, 1.57406803e-02f, + 1.25421761e-02f, 9.24754692e-03f, 5.88488640e-03f, 2.48280587e-03f, -9.29864758e-04f, -4.32426314e-03f, + -7.67179184e-03f, -1.09442952e-02f, -1.41143886e-02f, -1.71555974e-02f, -2.00425787e-02f, -2.27514891e-02f, + -2.52599054e-02f, -2.75472706e-02f, -2.95949315e-02f, -3.13863062e-02f, -3.29069832e-02f, -3.41450096e-02f, + -3.50907101e-02f, -3.57369992e-02f, -3.60793163e-02f, -3.61156751e-02f, -3.58467080e-02f, -3.52755740e-02f, + -3.44080617e-02f, -3.32523628e-02f, -3.18191314e-02f, -3.01213186e-02f, -2.81740846e-02f, -2.59946393e-02f, + -2.36021125e-02f, -2.10173975e-02f, -1.82629132e-02f, -1.53624700e-02f, -1.23410560e-02f, -9.22456599e-03f, + -6.03967755e-03f, -2.81350877e-03f, 4.26514319e-04f, 3.65292660e-03f, 6.83848944e-03f, 9.95638508e-03f, + 1.29804234e-02f, 1.58853076e-02f, 1.86468203e-02f, 2.12420277e-02f, 2.36494909e-02f, 2.58493792e-02f, + 2.78237450e-02f, 2.95565060e-02f, 3.10338053e-02f, 3.22438572e-02f, 3.31772716e-02f, 3.38269627e-02f, + 3.41883176e-02f, 3.42591610e-02f, 3.40397435e-02f, 3.35328606e-02f, 3.27436351e-02f, 3.16796573e-02f, + 3.03507246e-02f, 2.87689689e-02f, 2.69484839e-02f, 2.49054827e-02f, 2.26579086e-02f, 2.02254442e-02f, + 1.76292617e-02f, 1.48918382e-02f, 1.20368159e-02f, 9.08872468e-03f, 6.07283273e-03f, 3.01489838e-03f, + -5.90212194e-05f, -3.12287666e-03f, -6.15069532e-03f, -9.11695091e-03f, -1.19967033e-02f, -1.47657868e-02f, + -1.74011004e-02f, -1.98807214e-02f, -2.21841025e-02f, -2.42922632e-02f, -2.61879368e-02f, -2.78557311e-02f, + -2.92821801e-02f, -3.04559562e-02f, -3.13678907e-02f, -3.20110632e-02f, -3.23808087e-02f, -3.24749193e-02f, + -3.22933847e-02f, -3.18386269e-02f, -3.11153366e-02f, -3.01304804e-02f, -2.88932552e-02f, -2.74148734e-02f, + -2.57086673e-02f, -2.37898314e-02f, -2.16752343e-02f, -1.93835013e-02f, -1.69345799e-02f, -1.43497284e-02f, + -1.16513243e-02f, -8.86259097e-03f, -6.00748525e-03f, -3.11044903e-03f, -1.96143386e-04f, 2.71056658e-03f, + 5.58512222e-03f, 8.40318833e-03f, 1.11410160e-02f, 1.37756382e-02f, 1.62850338e-02f, 1.86482666e-02f, + 2.08457445e-02f, 2.28593437e-02f, 2.46725329e-02f, 2.62705694e-02f, 2.76405329e-02f, 2.87715470e-02f, + 2.96547092e-02f, 3.02833419e-02f, 3.06529059e-02f, 3.07610441e-02f, 3.06076742e-02f, 3.01949567e-02f, + 2.95271502e-02f, 2.86107876e-02f, 2.74543883e-02f, 2.60685701e-02f, 2.44657863e-02f, 2.26603655e-02f, + 2.06682557e-02f, 1.85070033e-02f, 1.61954603e-02f, 1.37537720e-02f, 1.12030588e-02f, 8.56537064e-03f, + 5.86336215e-03f, 3.12021752e-03f, 3.59345288e-04f, -2.39571357e-03f, -5.12158252e-03f, -7.79518527e-03f, + -1.03939536e-02f, -1.28961026e-02f, -1.52805838e-02f, -1.75275761e-02f, -1.96183935e-02f, -2.15357712e-02f, + -2.32639542e-02f, -2.47888545e-02f, -2.60981899e-02f, -2.71814567e-02f, -2.80302370e-02f, -2.86380088e-02f, + -2.90003996e-02f, -2.91151172e-02f, -2.89819544e-02f, -2.86028697e-02f, -2.79818317e-02f, -2.71249297e-02f, + -2.60401957e-02f, -2.47375751e-02f, -2.32288414e-02f, -2.15275091e-02f, -1.96486443e-02f, -1.76087964e-02f, + -1.54258426e-02f, -1.31187994e-02f, -1.07076937e-02f, -8.21335282e-03f, -5.65730582e-03f, -3.06143405e-03f, + -4.47990175e-04f, 2.16074548e-03f, 4.74260737e-03f, 7.27569124e-03f, 9.73864733e-03f, 1.21106824e-02f, + 1.43719841e-02f, 1.65036001e-02f, 1.84878471e-02f, 2.03083286e-02f, 2.19500531e-02f, 2.33996493e-02f, + 2.46453861e-02f, 2.56773512e-02f, 2.64874345e-02f, 2.70694463e-02f, 2.74192279e-02f, 2.75344951e-02f, + 2.74150667e-02f, 2.70627089e-02f, 2.64811913e-02f, 2.56761950e-02f, 2.46553112e-02f, 2.34279326e-02f, + 2.20051823e-02f, 2.03998041e-02f, 1.86260730e-02f, 1.66996483e-02f, 1.46373888e-02f, 1.24573628e-02f, + 1.01784699e-02f, 7.82046099e-03f, 5.40366356e-03f, 2.94886537e-03f, 4.77074685e-04f, -1.99056008e-03f, + -4.43309957e-03f, -6.82975366e-03f, -9.16032780e-03f, -1.14051392e-02f, -1.35453571e-02f, -1.55631186e-02f, + -1.74416221e-02f, -1.91653203e-02f, -2.07200521e-02f, -2.20931290e-02f, -2.32734389e-02f, -2.42515770e-02f, + -2.50198790e-02f, -2.55724740e-02f, -2.59053977e-02f, -2.60165073e-02f, -2.59056121e-02f, -2.55744100e-02f, + -2.50263861e-02f, -2.42670139e-02f, -2.33034172e-02f, -2.21444752e-02f, -2.08007704e-02f, -1.92843016e-02f, + -1.76086143e-02f, -1.57885066e-02f, -1.38399632e-02f, -1.17800468e-02f, -9.62665505e-03f, -7.39846180e-03f, + -5.11473979e-03f, -2.79509520e-03f, -4.59475153e-04f, 1.87219411e-03f, 4.18004886e-03f, 6.44446028e-03f, + 8.64630036e-03f, 1.07670050e-02f, 1.27887263e-02f, 1.46946183e-02f, 1.64687696e-02f, 1.80965074e-02f, + 1.95644657e-02f, 2.08606409e-02f, 2.19745569e-02f, 2.28973400e-02f, 2.36217678e-02f, 2.41423032e-02f, + 2.44552329e-02f, 2.45585559e-02f, 2.44521268e-02f, 2.41375247e-02f, 2.36181843e-02f, 2.28991883e-02f, + 2.19873596e-02f, 2.08911372e-02f, 1.96204854e-02f, 1.81868423e-02f, 1.66029686e-02f, 1.48829260e-02f, + 1.30418196e-02f, 1.10957823e-02f, 9.06176569e-03f, 6.95742371e-03f, 4.80095797e-03f, 2.61094572e-03f, + 4.06163422e-04f, -1.79448120e-03f, -3.97227507e-03f, -6.10867089e-03f, -8.18559133e-03f, -1.01855447e-02f, + -1.20916775e-02f, -1.38880736e-02f, -1.55597947e-02f, -1.70929424e-02f, -1.84749792e-02f, -1.96945768e-02f, + -2.07419008e-02f, -2.16086011e-02f, -2.22879060e-02f, -2.27746496e-02f, -2.30653527e-02f, -2.31582122e-02f, + -2.30530853e-02f, -2.27516002e-02f, -2.22569518e-02f, -2.15740851e-02f, -2.07094459e-02f, -1.96710504e-02f, + -1.84683607e-02f, -1.71122258e-02f, -1.56147530e-02f, -1.39891960e-02f, -1.22499260e-02f, -1.04121226e-02f, + -8.49187069e-03f, -6.50583812e-03f, -4.47121574e-03f, -2.40553061e-03f, -3.26560349e-04f, 1.74792849e-03f, + 3.80020986e-03f, 5.81284812e-03f, 7.76878436e-03f, 9.65152189e-03f, 1.14452321e-02f, 1.31348903e-02f, + 1.47064602e-02f, 1.61469015e-02f, 1.74443880e-02f, 1.85883329e-02f, 1.95694960e-02f, 2.03800747e-02f, + 2.10137416e-02f, 2.14657028e-02f, 2.17327470e-02f, 2.18132189e-02f, 2.17071096e-02f, 2.14159688e-02f, + 2.09429396e-02f, 2.02927056e-02f, 1.94714591e-02f, 1.84867806e-02f, 1.73476996e-02f, 1.60644888e-02f, + 1.46486021e-02f, 1.31126305e-02f, 1.14700918e-02f, 9.73543186e-03f, 7.92379251e-03f, 6.05090462e-03f, + 4.13301608e-03f, 2.18669055e-03f, 2.28581333e-04f, -1.72441072e-03f, -3.65572200e-03f, -5.54887990e-03f, + -7.38782061e-03f, -9.15706782e-03f, -1.08417082e-02f, -1.24276657e-02f, -1.39017311e-02f, -1.52516970e-02f, + -1.64664949e-02f, -1.75361817e-02f, -1.84521823e-02f, -1.92071599e-02f, -1.97953056e-02f, -2.02121243e-02f, + -2.04547147e-02f, -2.05216098e-02f, -2.04128534e-02f, -2.01300439e-02f, -1.96761990e-02f, -1.90558123e-02f, + -1.82748056e-02f, -1.73404276e-02f, -1.62612067e-02f, -1.50469098e-02f, -1.37084115e-02f, -1.22575769e-02f, + -1.07072432e-02f, -9.07102930e-03f, -7.36320826e-03f, -5.59869147e-03f, -3.79270806e-03f, -1.96092013e-03f, + -1.19027325e-04f, 1.71713152e-03f, 3.53191747e-03f, 5.30986343e-03f, 7.03590331e-03f, 8.69547560e-03f, + 1.02746006e-02f, 1.17601122e-02f, 1.31396009e-02f, 1.44016653e-02f, 1.55359973e-02f, 1.65332483e-02f, + 1.73855033e-02f, 1.80859434e-02f, 1.86291305e-02f, 1.90110277e-02f, 1.92289384e-02f, 1.92815880e-02f, + 1.91691688e-02f, 1.88932135e-02f, 1.84567183e-02f, 1.78639790e-02f, 1.71206377e-02f, 1.62336473e-02f, + 1.52110920e-02f, 1.40622274e-02f, 1.27973510e-02f, 1.14277163e-02f, 9.96541843e-03f, 8.42333112e-03f, + 6.81491991e-03f, 5.15420944e-03f, 3.45559138e-03f, 1.73374462e-03f, 3.49154958e-06f, -1.72033182e-03f, + -3.42300908e-03f, -5.09002877e-03f, -6.70728983e-03f, -8.26110592e-03f, -9.73843101e-03f, -1.11269177e-02f, + -1.24149972e-02f, -1.35920411e-02f, -1.46483675e-02f, -1.55754162e-02f, -1.63657097e-02f, -1.70130158e-02f, + -1.75123254e-02f, -1.78599156e-02f, -1.80533642e-02f, -1.80916471e-02f, -1.79749596e-02f, -1.77049199e-02f, + -1.72844059e-02f, -1.67175734e-02f, -1.60098348e-02f, -1.51677846e-02f, -1.41991369e-02f, -1.31126308e-02f, + -1.19180614e-02f, -1.06260158e-02f, -9.24795820e-03f, -7.79599691e-03f, -6.28282689e-03f, -4.72166017e-03f, + -3.12602130e-03f, -1.50971188e-03f, 1.13358008e-04f, 1.72924640e-03f, 3.32419869e-03f, 4.88457483e-03f, + 6.39719332e-03f, 7.84928507e-03f, 9.22860374e-03f, 1.05236737e-02f, 1.17237027e-02f, 1.28187631e-02f, + 1.37999219e-02f, 1.46591627e-02f, 1.53896448e-02f, 1.59855771e-02f, 1.64423748e-02f, 1.67566705e-02f, + 1.69263151e-02f, 1.69504088e-02f, 1.68293192e-02f, 1.65646048e-02f, 1.61591292e-02f, 1.56168830e-02f, + 1.49430466e-02f, 1.41438870e-02f, 1.32267343e-02f, 1.21999194e-02f, 1.10726150e-02f, 9.85491162e-03f, + 8.55755480e-03f, 7.19198626e-03f, 5.77013714e-03f, 4.30443841e-03f, 2.80758857e-03f, 1.29252809e-03f, + -2.27683018e-04f, -1.74000213e-03f, -3.23153173e-03f, -4.68956247e-03f, -6.10171563e-03f, -7.45612506e-03f, + -8.74136426e-03f, -9.94672023e-03f, -1.10621909e-02f, -1.20785406e-02f, -1.29874795e-02f, -1.37816456e-02f, + -1.44546479e-02f, -1.50012468e-02f, -1.54172106e-02f, -1.56995155e-02f, -1.58462779e-02f, -1.58567437e-02f, + -1.57313825e-02f, -1.54717967e-02f, -1.50807184e-02f, -1.45620705e-02f, -1.39207297e-02f, -1.31627253e-02f, + -1.22950111e-02f, -1.13254027e-02f, -1.02626834e-02f, -9.11627932e-03f, -7.89634415e-03f, -6.61364765e-03f, + -5.27939952e-03f, -3.90525708e-03f, -2.50314317e-03f, -1.08517576e-03f, 3.36418391e-04f, 1.74945190e-03f, + 3.14186033e-03f, 4.50178261e-03f, 5.81769448e-03f, 7.07851939e-03f, 8.27365386e-03f, 9.39310326e-03f, + 1.04276320e-02f, 1.13686527e-02f, 1.22085379e-02f, 1.29404450e-02f, 1.35585678e-02f, 1.40580446e-02f, + 1.44350939e-02f, 1.46869568e-02f, 1.48120098e-02f, 1.48096348e-02f, 1.46804295e-02f, 1.44259781e-02f, + 1.40489668e-02f, 1.35531325e-02f, 1.29432014e-02f, 1.22248563e-02f, 1.14046959e-02f, 1.04901687e-02f, + 9.48948107e-03f, 8.41156632e-03f, 7.26596347e-03f, 6.06280447e-03f, 4.81257444e-03f, 3.52622627e-03f, + 2.21492506e-03f, 8.89983592e-04f, -4.37153812e-04f, -1.75513167e-03f, -3.05265494e-03f, -4.31872834e-03f, + -5.54261874e-03f, -6.71396264e-03f, -7.82302244e-03f, -8.86045250e-03f, -9.81773278e-03f, -1.06869351e-02f, + -1.14610023e-02f, -1.21336754e-02f, -1.26995953e-02f, -1.31543908e-02f, -1.34945718e-02f, -1.37177266e-02f, + -1.38224110e-02f, -1.38082286e-02f, -1.36757739e-02f, -1.34266887e-02f, -1.30635886e-02f, -1.25900369e-02f, + -1.20105709e-02f, -1.13305978e-02f, -1.05563538e-02f, -9.69485926e-03f, -8.75389081e-03f, -7.74181164e-03f, + -6.66761679e-03f, -5.54076187e-03f, -4.37111830e-03f, -3.16893052e-03f, -1.94457115e-03f, -7.08705149e-04f, + 5.28079290e-04f, 1.75515870e-03f, 2.96204304e-03f, 4.13848585e-03f, 5.27451557e-03f, 6.36060039e-03f, + 7.38755863e-03f, 8.34692530e-03f, 9.23070802e-03f, 1.00316534e-02f, 1.07432528e-02f, 1.13597680e-02f, + 1.18763350e-02f, 1.22889283e-02f, 1.25944631e-02f, 1.27907515e-02f, 1.28765994e-02f, 1.28517102e-02f, + 1.27167966e-02f, 1.24734480e-02f, 1.21242371e-02f, 1.16725839e-02f, 1.11228281e-02f, 1.04800592e-02f, + 9.75022575e-03f, 8.93990424e-03f, 8.05644990e-03f, 7.10768601e-03f, 6.10205625e-03f, 5.04843878e-03f, + 3.95605458e-03f, 2.83441418e-03f, 1.69331277e-03f, 5.42568186e-04f, -6.07877124e-04f, -1.74818575e-03f, + -2.86860405e-03f, -3.95962685e-03f, -5.01201657e-03f, -6.01690058e-03f, -6.96589716e-03f, -7.85110424e-03f, + -8.66518231e-03f, -9.40145619e-03f, -1.00540095e-02f, -1.06175123e-02f, -1.10876024e-02f, -1.14606062e-02f, + -1.17337519e-02f, -1.19051415e-02f, -1.19737311e-02f, -1.19393909e-02f, -1.18028751e-02f, -1.15657387e-02f, + -1.12305357e-02f, -1.08005049e-02f, -1.02797519e-02f, -9.67318729e-03f, -8.98632838e-03f, -8.22543877e-03f, + -7.39737215e-03f, -6.50950785e-03f, -5.56975395e-03f, -4.58632875e-03f, -3.56792674e-03f, -2.52340823e-03f, + -1.46183597e-03f, -3.92391156e-04f, 6.75701684e-04f, 1.73331709e-03f, 2.77141530e-03f, 3.78118353e-03f, + 4.75407672e-03f, 5.68193005e-03f, 6.55698994e-03f, 7.37195674e-03f, 8.12013345e-03f, 8.79539509e-03f, + 9.39225030e-03f, 9.90597190e-03f, 1.03324819e-02f, 1.06685242e-02f, 1.09116177e-02f, 1.10600973e-02f, + 1.11130936e-02f, 1.10705983e-02f, 1.09333788e-02f, 1.07030445e-02f, 1.03819949e-02f, 9.97335332e-03f, + 9.48107464e-03f, 8.90968434e-03f, 8.26449756e-03f, 7.55132972e-03f, 6.77664458e-03f, 5.94731079e-03f, + 5.07073939e-03f, 4.15462520e-03f, 3.20700306e-03f, 2.23616222e-03f, 1.25050340e-03f, 2.58592562e-04f, + -7.31105992e-04f, -1.71003848e-03f, -2.66991104e-03f, -3.60254805e-03f, -4.50009626e-03f, -5.35500152e-03f, + -6.16013372e-03f, -6.90880302e-03f, -7.59484887e-03f, -8.21267759e-03f, -8.75730297e-03f, -9.22437062e-03f, + -9.61022818e-03f, -9.91196266e-03f, -1.01273334e-02f, -1.02549146e-02f, -1.02939949e-02f, -1.02446487e-02f, + -1.01077102e-02f, -9.88473930e-03f, -9.57804506e-03f, -9.19065219e-03f, -8.72623997e-03f, -8.18914967e-03f, + -7.58431711e-03f, -6.91725624e-03f, -6.19393169e-03f, -5.42085678e-03f, -4.60486090e-03f, -3.75314479e-03f, + -2.87318400e-03f, -1.97263669e-03f, -1.05936420e-03f, -1.41184633e-04f, 7.73935206e-04f, 1.67818033e-03f, + 2.56387121e-03f, 3.42348245e-03f, 4.24972968e-03f, 5.03575853e-03f, 5.77493594e-03f, 6.46117800e-03f, + 7.08885263e-03f, 7.65282423e-03f, 8.14856911e-03f, 8.57214716e-03f, 8.92027019e-03f, 9.19029194e-03f, + 9.38027470e-03f, 9.48895025e-03f, 9.51578399e-03f, 9.46091429e-03f, 9.32518284e-03f, 9.11016180e-03f, + 8.81806173e-03f, 8.45171440e-03f, 8.01466407e-03f, 7.51094572e-03f, 6.94521826e-03f, 6.32261691e-03f, + 5.64875255e-03f, 4.92963671e-03f, 4.17165548e-03f, 3.38149573e-03f, 2.56610069e-03f, 1.73253154e-03f, + 8.88083719e-04f, 4.00140997e-05f, -8.04377007e-04f, -1.63786496e-03f, -2.45336348e-03f, -3.24394120e-03f, + -4.00297149e-03f, -4.72406012e-03f, -5.40122825e-03f, -6.02886353e-03f, -6.60184564e-03f, -7.11547043e-03f, + -7.56567204e-03f, -7.94886879e-03f, -8.26207948e-03f, -8.50298133e-03f, -8.66984745e-03f, -8.76158174e-03f, + -8.77778600e-03f, -8.71866903e-03f, -8.58510255e-03f, -8.37858953e-03f, -8.10125332e-03f, -7.75580633e-03f, + -7.34555568e-03f, -6.87431135e-03f, -6.34642360e-03f, -5.76669768e-03f, -5.14031767e-03f, -4.47294897e-03f, + -3.77043291e-03f, -3.03903272e-03f, -2.28511456e-03f, -1.51527024e-03f, -7.36178447e-04f, 4.54225562e-05f, + 8.22859022e-04f, 1.58943109e-03f, 2.33866278e-03f, 3.06420334e-03f, 3.75990680e-03f, 4.42002538e-03f, + 5.03901750e-03f, 5.61180111e-03f, 6.13366220e-03f, 6.60043272e-03f, 7.00831931e-03f, 7.35414500e-03f, + 7.63524392e-03f, 7.84953557e-03f, 7.99547645e-03f, 8.07218955e-03f, 8.07933095e-03f, 8.01721906e-03f, + 7.88666864e-03f, 7.68919343e-03f, 7.42679720e-03f, 7.10202788e-03f, 6.71802523e-03f, 6.27832934e-03f, + 5.78702253e-03f, 5.24853339e-03f, 4.66776048e-03f, 4.04985033e-03f, 3.40032055e-03f, 2.72486114e-03f, + 2.02943382e-03f, 1.32005555e-03f, 6.02922229e-04f, -1.15810889e-04f, -8.29962401e-04f, -1.53344695e-03f, + -2.22024937e-03f, -2.88460828e-03f, -3.52090915e-03f, -4.12386103e-03f, -4.68844782e-03f, -5.21000854e-03f, + -5.68433641e-03f, -6.10753890e-03f, -6.47629357e-03f, -6.78770430e-03f, -7.03936807e-03f, -7.22944790e-03f, + -7.35662441e-03f, -7.42012069e-03f, -7.41971164e-03f, -7.35573757e-03f, -7.22905724e-03f, -7.04107429e-03f, + -6.79370122e-03f, -6.48940038e-03f, -6.13102314e-03f, -5.72192873e-03f, -5.26590521e-03f, -4.76707464e-03f, + -4.22993214e-03f, -3.65930825e-03f, -3.06022345e-03f, -2.43797793e-03f, -1.79803310e-03f, -1.14594988e-03f, + -4.87389180e-04f, 1.71985886e-04f, 8.26505744e-04f, 1.47057292e-03f, 2.09875564e-03f, 2.70572827e-03f, + 3.28638788e-03f, 3.83592350e-03f, 4.34975506e-03f, 4.82368759e-03f, 5.25383132e-03f, 5.63677359e-03f, + 5.96942535e-03f, 6.24924092e-03f, 6.47405650e-03f, 6.64226721e-03f, 6.75269253e-03f, 6.80469430e-03f, + 6.79815717e-03f, 6.73340631e-03f, 6.61130455e-03f, 6.43322863e-03f, 6.20094526e-03f, 5.91677710e-03f, + 5.58340169e-03f, 5.20393196e-03f, 4.78187614e-03f, 4.32106320e-03f, 3.82565711e-03f, 3.30005613e-03f, + 2.74895362e-03f, 2.17719303e-03f, 1.58978015e-03f, 9.91844057e-04f, 3.88540330e-04f, -2.14916878e-04f, + -8.13361192e-04f, -1.40168257e-03f, -1.97489740e-03f, -2.52818059e-03f, -3.05688539e-03f, -3.55662656e-03f, + -4.02326574e-03f, -4.45296958e-03f, -4.84228652e-03f, -5.18803438e-03f, -5.48755315e-03f, -5.73848611e-03f, + -5.93891991e-03f, -6.08745626e-03f, -6.18305471e-03f, -6.22520840e-03f, -6.21382472e-03f, -6.14928419e-03f, + -6.03244633e-03f, -5.86455879e-03f, -5.64736180e-03f, -5.38296537e-03f, -5.07389363e-03f, -4.72301916e-03f, + -4.33361321e-03f, -3.90915761e-03f, -3.45353173e-03f, -2.97077347e-03f, -2.46516689e-03f, -1.94119584e-03f, + -1.40340595e-03f, -8.56512644e-04f, -3.05232133e-04f, 2.45691031e-04f, 7.91538060e-04f, 1.32763724e-03f, + 1.84949345e-03f, 2.35267547e-03f, 2.83299113e-03f, 3.28645035e-03f, 3.70931698e-03f, 4.09812665e-03f, + 4.44973511e-03f, 4.76135341e-03f, 5.03050354e-03f, 5.25513155e-03f, 5.43353323e-03f, 5.56447821e-03f, + 5.64705544e-03f, 5.68083601e-03f, 5.66583437e-03f, 5.60238431e-03f, 5.49135375e-03f, 5.33391723e-03f, + 5.13169207e-03f, 4.88664671e-03f, 4.60113202e-03f, 4.27780860e-03f, 3.91964875e-03f, 3.52989866e-03f, + 3.11212090e-03f, 2.66999053e-03f, 2.20744344e-03f, 1.72859110e-03f, 1.23756351e-03f, 7.38678150e-04f, + 2.36236760e-04f, -2.65462378e-04f, -7.62072815e-04f, -1.24943395e-03f, -1.72337956e-03f, -2.17993754e-03f, + -2.61530935e-03f, -3.02588421e-03f, -3.40825196e-03f, -3.75935360e-03f, -4.07630652e-03f, -4.35660760e-03f, + -4.59808398e-03f, -4.79883718e-03f, -4.95743843e-03f, -5.07271280e-03f, -5.14393833e-03f, -5.17077608e-03f, + -5.15318763e-03f, -5.09164480e-03f, -4.98686807e-03f, -4.84002285e-03f, -4.65260103e-03f, -4.42642977e-03f, + -4.16366446e-03f, -3.86678300e-03f, -3.53847751e-03f, -3.18177292e-03f, -2.79986847e-03f, -2.39618401e-03f, + -1.97429017e-03f, -1.53788782e-03f, -1.09083664e-03f, -6.36973406e-04f, -1.80264329e-04f, 2.75399352e-04f, + 7.26104424e-04f, 1.16802598e-03f, 1.59744046e-03f, 2.01073128e-03f, 2.40446819e-03f, 2.77538562e-03f, + 3.12044615e-03f, 3.43683203e-03f, 3.72202393e-03f, 3.97374850e-03f, 4.19002854e-03f, 4.36925418e-03f, + 4.51006070e-03f, 4.61152219e-03f, 4.67293053e-03f, 4.69404975e-03f, 4.67490366e-03f, 4.61589307e-03f, + 4.51775252e-03f, 4.38154991e-03f, 4.20868532e-03f, 4.00082377e-03f, 3.75997274e-03f, 3.48836415e-03f, + 3.18851504e-03f, 2.86314343e-03f, 2.51519536e-03f, 2.14776743e-03f, 1.76411750e-03f, 1.36763070e-03f, + 9.61751835e-04f, 5.50052405e-04f, 1.36015058e-04f, -2.76720943e-04f, -6.84698152e-04f, -1.08442387e-03f, + -1.47253691e-03f, -1.84578853e-03f, -2.20105818e-03f, -2.53544188e-03f, -2.84616998e-03f, -3.13076058e-03f, + -3.38689733e-03f, -3.61260297e-03f, -3.80606518e-03f, -3.96589267e-03f, -4.09087232e-03f, -4.18013173e-03f, + -4.23315965e-03f, -4.24970953e-03f, -4.22981560e-03f, -4.17392494e-03f, -4.08267808e-03f, -3.95709577e-03f, + -3.79845153e-03f, -3.60829670e-03f, -3.38844338e-03f, -3.14094669e-03f, -2.86809742e-03f, -2.57237442e-03f, + -2.25643831e-03f, -1.92312165e-03f, -1.57535841e-03f, -1.21624129e-03f, -8.48868370e-04f, -4.76457354e-04f, + -1.02227062e-04f, 2.70659894e-04f, 6.38948957e-04f, 9.99596773e-04f, 1.34950884e-03f, 1.68579412e-03f, + 2.00565112e-03f, 2.30644176e-03f, 2.58570970e-03f, 2.84121989e-03f, 3.07087670e-03f, 3.27296771e-03f, + 3.44584695e-03f, 3.58825627e-03f, 3.69915439e-03f, 3.77779535e-03f, 3.82369144e-03f, 3.83666312e-03f, + 3.81678507e-03f, 3.76444486e-03f, 3.68027755e-03f, 3.56519883e-03f, 3.42038694e-03f, 3.24725992e-03f, + 3.04745181e-03f, 2.82287635e-03f, 2.57555610e-03f, 2.30778342e-03f, 2.02193938e-03f, 1.72060684e-03f, + 1.40642226e-03f, 1.08218540e-03f, 7.50708128e-04f, 4.14852040e-04f, 7.75468400e-05f, -2.58336678e-04f, + -5.89954675e-04f, -9.14464553e-04f, -1.22917409e-03f, -1.53142096e-03f, -1.81874942e-03f, -2.08875765e-03f, + -2.33925204e-03f, -2.56824046e-03f, -2.77387464e-03f, -2.95457151e-03f, -3.10891286e-03f, -3.23576957e-03f, + -3.33422309e-03f, -3.40361730e-03f, -3.44352432e-03f, -3.45380945e-03f, -3.43454926e-03f, -3.38612359e-03f, + -3.30910238e-03f, -3.20434413e-03f, -3.07289782e-03f, -2.91605448e-03f, -2.73534798e-03f, -2.53242439e-03f, + -2.30918427e-03f, -2.06766744e-03f, -1.81002532e-03f, -1.53857461e-03f, -1.25572213e-03f, -9.63956082e-04f, + -6.65804929e-04f, -3.63875198e-04f, -6.07622519e-05f, 2.40955893e-04f, 5.38685581e-04f, 8.29936911e-04f, + 1.11224977e-03f, 1.38328230e-03f, 1.64080028e-03f, 1.88265574e-03f, 2.10694670e-03f, 2.31181334e-03f, + 2.49567938e-03f, 2.65707799e-03f, 2.79477329e-03f, 2.90778929e-03f, 2.99526804e-03f, 3.05666792e-03f, + 3.09159989e-03f, 3.09996074e-03f, 3.08183486e-03f, 3.03757314e-03f, 2.96768997e-03f, 2.87296391e-03f, + 2.75438271e-03f, 2.61305979e-03f, 2.45041225e-03f, 2.26792371e-03f, 2.06728115e-03f, 1.85034398e-03f, + 1.61901728e-03f, 1.37543970e-03f, 1.12168235e-03f, 8.60048928e-04f, 5.92781787e-04f, 3.22217129e-04f, + 5.06437951e-05f, -2.19547817e-04f, -4.86132510e-04f, -7.46817210e-04f, -9.99443627e-04f, -1.24188233e-03f, + -1.47217245e-03f, -1.68839648e-03f, -1.88883105e-03f, -2.07184785e-03f, -2.23601745e-03f, -2.38006048e-03f, + -2.50288118e-03f, -2.60358292e-03f, -2.68144174e-03f, -2.73595307e-03f, -2.76679595e-03f, -2.77388624e-03f, + -2.75729794e-03f, -2.71735188e-03f, -2.65451985e-03f, -2.56952130e-03f, -2.46319204e-03f, -2.33660956e-03f, + -2.19096493e-03f, -2.02765268e-03f, -1.84815939e-03f, -1.65412932e-03f, -1.44731483e-03f, -1.22956426e-03f, + -1.00280075e-03f, -7.69022668e-04f, -5.30268510e-04f, -2.88586883e-04f, -4.60956253e-05f, 1.95186584e-04f, + 4.33161045e-04f, 6.65873263e-04f, 8.91328897e-04f, 1.10770620e-03f, 1.31316296e-03f, 1.50610067e-03f, + 1.68489795e-03f, 1.84814923e-03f, 1.99458512e-03f, 2.12304250e-03f, 2.23258384e-03f, 2.32237953e-03f, + 2.39181962e-03f, 2.44043032e-03f, 2.46796938e-03f, 2.47430968e-03f, 2.45957831e-03f, 2.42401283e-03f, + 2.36808884e-03f, 2.29238471e-03f, 2.19773378e-03f, 2.08501666e-03f, 1.95534528e-03f, 1.80993801e-03f, + 1.65014053e-03f, 1.47739854e-03f, 1.29329221e-03f, 1.09944593e-03f, 8.97596290e-04f, 6.89486470e-04f, + 4.76967544e-04f, 2.61847472e-04f, 4.59979030e-05f, -1.68770369e-04f, -3.80612759e-04f, -5.87744421e-04f, + -7.88452414e-04f, -9.81081718e-04f, -1.16402219e-03f, -1.33580811e-03f, -1.49504859e-03f, -1.64047131e-03f, + -1.77095587e-03f, -1.88548340e-03f, -1.98318254e-03f, -2.06335667e-03f, -2.12544333e-03f, -2.16903096e-03f, + -2.19389731e-03f, -2.19994674e-03f, -2.18726700e-03f, -2.15609170e-03f, -2.10683457e-03f, -2.04002290e-03f, + -1.95633800e-03f, -1.85665258e-03f, -1.74189023e-03f, -1.61313165e-03f, -1.47159921e-03f, -1.31856217e-03f, + -1.15541374e-03f, -9.83590913e-04f, -8.04645529e-04f, -6.20138811e-04f, -4.31664744e-04f, -2.40859759e-04f, + -4.93718861e-05f, 1.41183920e-04f, 3.29184443e-04f, 5.13049545e-04f, 6.91252710e-04f, 8.62329668e-04f, + 1.02486089e-03f, 1.17753306e-03f, 1.31912530e-03f, 1.44851584e-03f, 1.56468190e-03f, 1.66675270e-03f, + 1.75393226e-03f, 1.82562545e-03f, 1.88129935e-03f, 1.92062935e-03f, 1.94336360e-03f, 1.94946381e-03f, + 1.93898469e-03f, 1.91211060e-03f, 1.86925265e-03f, 1.81081128e-03f, 1.73745800e-03f, 1.64989979e-03f, + 1.54896085e-03f, 1.43565148e-03f, 1.31095906e-03f, 1.17607031e-03f, 1.03219054e-03f, 8.80596006e-04f, + 7.22634695e-04f, 5.59715925e-04f, 3.93223384e-04f, 2.24602808e-04f, 5.53223372e-05f, -1.13204206e-04f, + -2.79527886e-04f, -4.42273875e-04f, -6.00090187e-04f, -7.51646708e-04f, -8.95738714e-04f, -1.03117771e-03f, + -1.15687770e-03f, -1.27187587e-03f, -1.37523688e-03f, -1.46618576e-03f, -1.54403989e-03f, -1.60825931e-03f, + -1.65836399e-03f, -1.69405240e-03f, -1.71514183e-03f, -1.72154028e-03f, -1.71331327e-03f, -1.69063272e-03f, + -1.65381037e-03f, -1.60326168e-03f, -1.53948863e-03f, -1.46318779e-03f, -1.37503217e-03f, -1.27591969e-03f, + -1.16672308e-03f, -1.04846883e-03f, -9.22232848e-04f, -7.89108246e-04f, -6.50329911e-04f, -5.07057241e-04f, + -3.60579584e-04f, -2.12138548e-04f, -6.30166060e-05f, 8.55107333e-05f, 2.32212191e-04f, 3.75851456e-04f, + 5.15213418e-04f, 6.49182851e-04f, 7.76642588e-04f, 8.96585347e-04f, 1.00803198e-03f, 1.11010987e-03f, + 1.20203475e-03f, 1.28308439e-03f, 1.35268783e-03f, 1.41030687e-03f, 1.45558664e-03f, 1.48819124e-03f, + 1.50798717e-03f, 1.51486502e-03f, 1.50888467e-03f, 1.49022209e-03f, 1.45906012e-03f, 1.41583581e-03f, + 1.36095722e-03f, 1.29499749e-03f, 1.21859138e-03f, 1.13249419e-03f, 1.03745344e-03f, 9.34384957e-04f, + 8.24209226e-04f, 7.07921644e-04f, 5.86535461e-04f, 4.61118668e-04f, 3.32797940e-04f, 2.02615430e-04f, + 7.17560319e-05f, -5.87215139e-05f, -1.87700771e-04f, -3.14093799e-04f, -4.36855019e-04f, -5.54982470e-04f, + -6.67514567e-04f, -7.73539543e-04f, -8.72216549e-04f, -9.62754726e-04f, -1.04446836e-03f, -1.11673823e-03f, + -1.17901020e-03f, -1.23084835e-03f, -1.27191263e-03f, -1.30189831e-03f, -1.32066941e-03f, -1.32816613e-03f, + -1.32437715e-03f, -1.30944714e-03f, -1.28360668e-03f, -1.24710492e-03f, -1.20038313e-03f, -1.14391116e-03f, + -1.07822250e-03f, -1.00394823e-03f, -9.21799577e-04f, -8.32520513e-04f, -7.36916195e-04f, -6.35853312e-04f, + -5.30218398e-04f, -4.20950684e-04f, -3.08981087e-04f, -1.95310152e-04f, -8.08721649e-05f, 3.33481785e-05f, + 1.46369769e-04f, 2.57271691e-04f, 3.65123878e-04f, 4.69053422e-04f, 5.68205019e-04f, 6.61777482e-04f, + 7.49035427e-04f, 8.29295760e-04f, 9.01919035e-04f, 9.66370937e-04f, 1.02218113e-03f, 1.06892877e-03f, + 1.10630552e-03f, 1.13406370e-03f, 1.15204451e-03f, 1.16019052e-03f, 1.15848806e-03f, 1.14706630e-03f, + 1.12606449e-03f, 1.09574589e-03f, 1.05645362e-03f, 1.00859266e-03f, 9.52601766e-04f, 8.89057609e-04f, + 8.18535938e-04f, 7.41697389e-04f, 6.59241262e-04f, 5.71884368e-04f, 4.80414698e-04f, 3.85677252e-04f, + 2.88406796e-04f, 1.89536836e-04f, 8.98491837e-05f, -9.79888746e-06f, -1.08531507e-04f, -2.05575498e-04f, + -3.00092231e-04f, -3.91327952e-04f, -4.78537671e-04f, -5.61003964e-04f, -6.38090388e-04f, -7.09209697e-04f, + -7.73747838e-04f, -8.31297964e-04f, -8.81364804e-04f, -9.23641236e-04f, -9.57793553e-04f, -9.83624619e-04f, + -1.00098424e-03f, -1.00979404e-03f, -1.01003977e-03f, -1.00180772e-03f, -9.85219816e-04f, -9.60506778e-04f, + -9.27905874e-04f, -8.87790902e-04f, -8.40553609e-04f, -7.86632276e-04f, -7.26559669e-04f, -6.60872173e-04f, + -5.90177860e-04f, -5.15099219e-04f, -4.36341554e-04f, -3.54526447e-04f, -2.70436804e-04f, -1.84757234e-04f, + -9.82406108e-05f, -1.16228429e-05f, 7.44116225e-05f, 1.59099493e-04f, 2.41739119e-04f, 3.21707034e-04f, + 3.98276352e-04f, 4.70887555e-04f, 5.38973046e-04f, 6.01940918e-04f, 6.59368174e-04f, 7.10783030e-04f, + 7.55802336e-04f, 7.94127086e-04f, 8.25478803e-04f, 8.49639386e-04f, 8.66487952e-04f, 8.75935969e-04f, + 8.77948893e-04f, 8.72611584e-04f, 8.59994515e-04f, 8.40271458e-04f, 8.13696181e-04f, 7.80491851e-04f, + 7.41053306e-04f, 6.95727202e-04f, 6.44936090e-04f, 5.89181503e-04f, 5.28946796e-04f, 4.64790448e-04f, + 3.97272420e-04f, 3.27000597e-04f, 2.54559578e-04f, 1.80597276e-04f, 1.05760446e-04f, 3.06209047e-05f, + -4.41172003e-05f, -1.17884760e-04f, -1.90032814e-04f, -2.60000039e-04f, -3.27213235e-04f, -3.91110007e-04f, + -4.51226928e-04f, -5.07042112e-04f, -5.58194586e-04f, -6.04189222e-04f, -6.44816381e-04f, -6.79653847e-04f, + -7.08557315e-04f, -7.31282579e-04f, -7.47702169e-04f, -7.57731688e-04f, -7.61359812e-04f, -7.58589885e-04f, + -7.49503361e-04f, -7.34226582e-04f, -7.12935677e-04f, -6.85882645e-04f, -6.53307567e-04f, -6.15569562e-04f, + -5.72978650e-04f, -5.25977418e-04f, -4.74963705e-04f, -4.20426590e-04f, -3.62819514e-04f, -3.02647353e-04f, + -2.40497241e-04f, -1.76810216e-04f, -1.12210871e-04f, -4.71976690e-05f, 1.76624641e-05f, 8.18440593e-05f, + 1.44804207e-04f, 2.06021410e-04f, 2.65025446e-04f, 3.21327783e-04f, 3.74487008e-04f, 4.24062432e-04f, + 4.69715655e-04f, 5.11042943e-04f, 5.47794530e-04f, 5.79655168e-04f, 6.06446384e-04f, 6.27934546e-04f, + 6.44010762e-04f, 6.54614698e-04f, 6.59636425e-04f, 6.59157826e-04f, 6.53158826e-04f, 6.41794049e-04f, + 6.25154916e-04f, 6.03470855e-04f, 5.76917242e-04f, 5.45789736e-04f, 5.10368292e-04f, 4.70998661e-04f, + 4.28021656e-04f, 3.81834126e-04f, 3.32863326e-04f, 2.81489629e-04f, 2.28231239e-04f, 1.73484261e-04f, + 1.17756607e-04f, 6.14881351e-05f, 5.17778269e-06f, -5.07352374e-05f, -1.05745987e-04f, -1.59454662e-04f, + -2.11394268e-04f, -2.61151905e-04f, -3.08351703e-04f, -3.52598590e-04f, -3.93545002e-04f, -4.30916147e-04f, + -4.64387406e-04f, -4.93756593e-04f, -5.18755281e-04f, -5.39265493e-04f, -5.55137934e-04f, -5.66259303e-04f, + -5.72606783e-04f, -5.74140344e-04f, -5.70903292e-04f, -5.62934741e-04f, -5.50388898e-04f, -5.33351962e-04f, + -5.12028510e-04f, -4.86612455e-04f, -4.57392981e-04f, -4.24578939e-04f, -3.88503808e-04f, -3.49487518e-04f, + -3.07895836e-04f, -2.64036522e-04f, -2.18356445e-04f, -1.71198300e-04f, -1.22998901e-04f, -7.41392080e-05f, + -2.50280393e-05f, 2.38852047e-05f, 7.22663332e-05f, 1.19659647e-04f, 1.65718806e-04f, 2.10055385e-04f, + 2.52324173e-04f, 2.92190427e-04f, 3.29337577e-04f, 3.63510150e-04f, 3.94385715e-04f, 4.21803288e-04f, + 4.45519433e-04f, 4.65391876e-04f, 4.81270460e-04f, 4.93057625e-04f, 5.00688030e-04f, 5.04121708e-04f, + 5.03379627e-04f, 4.98485604e-04f, 4.89499566e-04f, 4.76539317e-04f, 4.59760023e-04f, 4.39274612e-04f, + 4.15334876e-04f, 3.88103885e-04f, 3.57902146e-04f, 3.24908089e-04f, 2.89490480e-04f, 2.51922687e-04f, + 2.12512220e-04f, 1.71637404e-04f, 1.29609890e-04f, 8.67866183e-05f, 4.35312276e-05f, 1.98808307e-07f, + -4.28589070e-05f, -8.52865394e-05f, -1.26765698e-04f, -1.66922292e-04f, -2.05456466e-04f, -2.42095652e-04f, + -2.76487494e-04f, -3.08425602e-04f, -3.37638832e-04f, -3.63923042e-04f, -3.87022898e-04f, -4.06875144e-04f, + -4.23245129e-04f, -4.36071615e-04f, -4.45236993e-04f, -4.50724682e-04f, -4.52491230e-04f, -4.50548104e-04f, + -4.44936790e-04f, -4.35725612e-04f, -4.22987381e-04f, -4.06882738e-04f, -3.87548587e-04f, -3.65123104e-04f, + -3.39860288e-04f, -3.11947486e-04f, -2.81618569e-04f, -2.49166817e-04f, -2.14824344e-04f, -1.78876370e-04f, + -1.41684861e-04f, -1.03466427e-04f, -6.45996088e-05f, -2.53738050e-05f, 1.39035721e-05f, 5.28977578e-05f, + 9.13010773e-05f, 1.28809554e-04f, 1.65139924e-04f, 2.00005346e-04f, 2.33095696e-04f, 2.64232233e-04f, + 2.93070034e-04f, 3.19508024e-04f, 3.43252648e-04f, 3.64165224e-04f, 3.82074036e-04f, 3.96868082e-04f, + 4.08408250e-04f, 4.16671952e-04f, 4.21556517e-04f, 4.23035822e-04f, 4.21172111e-04f, 4.15928838e-04f, + 4.07377025e-04f, 3.95568598e-04f, 3.80628038e-04f, 3.62729177e-04f, 3.41921136e-04f, 3.18489958e-04f, + 2.92497406e-04f, 2.64266550e-04f, 2.33955571e-04f, 2.01809261e-04f, 1.68092145e-04f, 1.33141461e-04f, + 9.71043460e-05f, 6.03452880e-05f, 2.31264055e-05f, -1.43105089e-05f, -5.15607083e-05f, -8.84833364e-05f, + -1.24679461e-04f, -1.59910519e-04f, -1.93952723e-04f, -2.26496145e-04f, -2.57307566e-04f, -2.86175538e-04f, + -3.12853472e-04f, -3.37140613e-04f, -3.58914997e-04f, -3.77932329e-04f, -3.94117065e-04f, -4.07317063e-04f, + -4.17422308e-04f, -4.24419479e-04f, -4.28161231e-04f, -4.28700484e-04f, -4.26016659e-04f, -4.20088126e-04f, + -4.11009185e-04f, -3.98835037e-04f, -3.83585114e-04f, -3.65493072e-04f, -3.44616197e-04f, -3.21064387e-04f, + -2.95119418e-04f, -2.66863117e-04f, -2.36549174e-04f, -2.04391686e-04f, -1.70585806e-04f, -1.35432614e-04f, + -9.91006984e-05f, -6.19152828e-05f, -2.41012311e-05f, 1.40621144e-05f, 5.22867497e-05f, 9.03199843e-05f, + 1.27917614e-04f, 1.64740292e-04f, 2.00634478e-04f, 2.35261402e-04f, 2.68377430e-04f, 2.99818019e-04f, + 3.29273634e-04f, 3.56562766e-04f, 3.81532332e-04f, 4.03948113e-04f, 4.23655375e-04f, 4.40488930e-04f, + 4.54376777e-04f, 4.65137195e-04f, 4.72679704e-04f, 4.77014073e-04f, 4.77982201e-04f, 4.75625277e-04f, + 4.69878507e-04f, 4.60802987e-04f, 4.48367418e-04f, 4.32641679e-04f, 4.13709630e-04f, 3.91634147e-04f, + 3.66512902e-04f, 3.38481392e-04f, 3.07634938e-04f, 2.74189182e-04f, 2.38229594e-04f, 1.99985879e-04f, + 1.59632210e-04f, 1.17351364e-04f, 7.33404728e-05f, 2.78844831e-05f, -1.89099461e-05f, -6.67343638e-05f, + -1.15367449e-04f, -1.64649983e-04f, -2.14224348e-04f, -2.64019844e-04f, -3.13654244e-04f, -3.62990333e-04f, + -4.11800705e-04f, -4.59821928e-04f, -5.06946486e-04f, -5.52847863e-04f, -5.97397068e-04f, -6.40454770e-04f, + -6.81765968e-04f, -7.21210131e-04f, -7.58634477e-04f, -7.93939572e-04f, -8.26964876e-04f, -8.57585335e-04f, + -8.85733438e-04f, -9.11351007e-04f, -9.34300512e-04f, -9.54617442e-04f, -9.72159416e-04f, -9.87012089e-04f, + -9.99095133e-04f, -1.00846242e-03f, -1.01506022e-03f, -1.01897105e-03f, -1.02021427e-03f, -1.01887259e-03f, + -1.01497557e-03f, -1.00861358e-03f, -9.99877741e-04f, -9.88823136e-04f, -9.75617693e-04f, -9.60303769e-04f, + -9.43035535e-04f, -9.23922797e-04f, -9.03105429e-04f, -8.80708716e-04f, -8.56853281e-04f, -8.31685264e-04f, + -8.05348207e-04f, -7.77961627e-04f, -7.49713086e-04f, -7.20674604e-04f, -6.91032783e-04f, -6.60888020e-04f, + -6.30372917e-04f, -5.99673349e-04f, -5.68830563e-04f, -5.38013304e-04f, -5.07353303e-04f, -4.76915043e-04f, + -4.46832926e-04f, -4.17179291e-04f, -3.88083307e-04f, -3.59575024e-04f, -3.31820735e-04f, -3.04804303e-04f, + -2.78616041e-04f, -2.53335964e-04f, -2.28986996e-04f, -2.05619529e-04f, -1.83318449e-04f, -1.61979425e-04f, + -1.41791423e-04f, -1.22648816e-04f, -1.04625498e-04f, -8.77122910e-05f, -7.18653457e-05f, -5.71787106e-05f, + -4.34807639e-05f, -3.09618857e-05f, -1.94074401e-05f, -8.88017971e-06f, 6.09625220e-07f, 9.14020334e-06f, + 1.67805558e-05f, 2.35369965e-05f, 2.94278194e-05f, 3.45049751e-05f, 3.88373828e-05f, 4.24291966e-05f, + 4.53445665e-05f, 4.76965834e-05f, 4.93395567e-05f, 5.05392111e-05f, 5.12257065e-05f, 5.14579340e-05f, + 5.12651750e-05f, 5.07312551e-05f, 4.98486765e-05f, 4.87082573e-05f, 4.73439631e-05f, 4.56740817e-05f, + 4.38653618e-05f, 4.19399075e-05f, 3.99125668e-05f, 3.77616021e-05f, 3.56135997e-05f, 3.33554815e-05f, + 3.11656899e-05f, 2.89038150e-05f, 2.67281634e-05f, 2.46192762e-05f, 2.24899205e-05f, 2.04698700e-05f, + 1.84927655e-05f, 1.66762886e-05f, 1.49393771e-05f, 1.32258081e-05f, 1.16985586e-05f, 1.01874391e-05f, + 8.99882100e-06f, 7.61267073e-06f, 6.57702907e-06f, 5.59829210e-06f, 4.27698546e-06f, 1.03248674e-05f, +}; diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 8b32ada296..a1a5915a8c 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "InboundAudioStream.h" @@ -58,6 +59,9 @@ void InboundAudioStream::reset() { _isStarved = true; _hasStarted = false; resetStats(); + // FIXME: calling cleanupCodec() seems to be the cause of the buzzsaw -- we get an assert + // after this is called in AudioClient. Ponder and fix... + // cleanupCodec(); } void InboundAudioStream::resetStats() { @@ -99,12 +103,12 @@ void InboundAudioStream::perSecondCallbackForUpdatingStats() { } int InboundAudioStream::parseData(ReceivedMessage& message) { - // parse sequence number and track it quint16 sequence; message.readPrimitive(&sequence); SequenceNumberStats::ArrivalInfo arrivalInfo = _incomingSequenceNumberStats.sequenceNumberReceived(sequence, message.getSourceID()); + QString codecInPacket = message.readString(); packetReceivedUpdateTimingStats(); @@ -114,7 +118,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) { 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) { case SequenceNumberStats::Early: { @@ -129,9 +133,22 @@ int InboundAudioStream::parseData(ReceivedMessage& message) { case SequenceNumberStats::OnTime: { // Packet is on time; parse its data to the ringbuffer if (message.getType() == PacketType::SilentAudioFrame) { + // FIXME - Some codecs need to know about these silent frames... and can produce better output writeDroppableSilentSamples(networkSamples); } else { - parseAudioData(message.getType(), message.readWithoutCopy(message.getBytesLeftToRead()), networkSamples); + // note: PCM and no codec are identical + bool selectedPCM = _selectedCodecName == "pcm" || _selectedCodecName == ""; + bool packetPCM = codecInPacket == "pcm" || codecInPacket == ""; + if (codecInPacket == _selectedCodecName || (packetPCM && selectedPCM)) { + auto afterProperties = message.readWithoutCopy(message.getBytesLeftToRead()); + parseAudioData(message.getType(), afterProperties); + } else { + qDebug() << "Codec mismatch: expected" << _selectedCodecName << "got" << codecInPacket << "writing silence"; + writeDroppableSilentSamples(networkSamples); + // inform others of the mismatch + auto sendingNode = DependencyManager::get()->nodeWithUUID(message.getSourceID()); + emit mismatchedAudioCodec(sendingNode, _selectedCodecName); + } } break; } @@ -172,16 +189,27 @@ int InboundAudioStream::parseStreamProperties(PacketType type, const QByteArray& return sizeof(quint16); } else { // mixed audio packets do not have any info between the seq num and the audio data. - numAudioSamples = packetAfterSeqNum.size() / sizeof(int16_t); + numAudioSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; return 0; } } -int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { - return _ringBuffer.writeData(packetAfterStreamProperties.data(), numAudioSamples * sizeof(int16_t)); +int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) { + QByteArray decodedBuffer; + if (_decoder) { + _decoder->decode(packetAfterStreamProperties, decodedBuffer); + } else { + decodedBuffer = packetAfterStreamProperties; + } + auto actualSize = decodedBuffer.size(); + return _ringBuffer.writeData(decodedBuffer.data(), actualSize); } int InboundAudioStream::writeDroppableSilentSamples(int silentSamples) { + if (_decoder) { + _decoder->trackLostFrames(silentSamples); + } + // calculate how many silent frames we should drop. int samplesPerFrame = _ringBuffer.getNumFrameSamples(); int desiredJitterBufferFramesPlusPadding = _desiredJitterBufferFrames + DESIRED_JITTER_BUFFER_FRAMES_PADDING; @@ -497,3 +525,21 @@ float calculateRepeatedFrameFadeFactor(int indexOfRepeat) { return 0.0f; } +void InboundAudioStream::setupCodec(CodecPluginPointer codec, const QString& codecName, int numChannels) { + cleanupCodec(); // cleanup any previously allocated coders first + _codec = codec; + _selectedCodecName = codecName; + if (_codec) { + _decoder = codec->createDecoder(AudioConstants::SAMPLE_RATE, numChannels); + } +} + +void InboundAudioStream::cleanupCodec() { + // release any old codec encoder/decoder first... + if (_codec) { + if (_decoder) { + _codec->releaseDecoder(_decoder); + _decoder = nullptr; + } + } +} diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index 3f641f1ba4..af79ff6164 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -12,12 +12,15 @@ #ifndef hifi_InboundAudioStream_h #define hifi_InboundAudioStream_h +#include #include #include #include #include #include +#include + #include "AudioRingBuffer.h" #include "MovingMinMaxAvg.h" #include "SequenceNumberStats.h" @@ -103,6 +106,7 @@ public: public: InboundAudioStream(int numFrameSamples, int numFramesCapacity, const Settings& settings); + ~InboundAudioStream() { cleanupCodec(); } void reset(); virtual void resetStats(); @@ -174,6 +178,12 @@ public: void setReverb(float reverbTime, float wetLevel); void clearReverb() { _hasReverb = false; } + void setupCodec(CodecPluginPointer codec, const QString& codecName, int numChannels); + void cleanupCodec(); + +signals: + void mismatchedAudioCodec(SharedNodePointer sendingNode, const QString& desiredCodec); + public slots: /// This function should be called every second for all the stats to function properly. If dynamic jitter buffers /// is enabled, those stats are used to calculate _desiredJitterBufferFrames. @@ -201,7 +211,7 @@ protected: /// parses the audio data in the network packet. /// default implementation assumes packet contains raw audio samples after stream properties - virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples); + virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties); /// writes silent samples to the buffer that may be dropped to reduce latency caused by the buffer virtual int writeDroppableSilentSamples(int silentSamples); @@ -267,6 +277,10 @@ protected: bool _hasReverb; float _reverbTime; float _wetLevel; + + CodecPluginPointer _codec; + QString _selectedCodecName; + Decoder* _decoder{ nullptr }; }; float calculateRepeatedFrameFadeFactor(int indexOfRepeat); diff --git a/libraries/audio/src/InjectedAudioStream.cpp b/libraries/audio/src/InjectedAudioStream.cpp index 54e0f92bea..ccd581959f 100644 --- a/libraries/audio/src/InjectedAudioStream.cpp +++ b/libraries/audio/src/InjectedAudioStream.cpp @@ -33,6 +33,7 @@ const uchar MAX_INJECTOR_VOLUME = 255; int InjectedAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { + // setup a data stream to read from this packet QDataStream packetStream(packetAfterSeqNum); diff --git a/libraries/audio/src/MixedProcessedAudioStream.cpp b/libraries/audio/src/MixedProcessedAudioStream.cpp index d236ac7aad..728deae0b1 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.cpp +++ b/libraries/audio/src/MixedProcessedAudioStream.cpp @@ -42,12 +42,18 @@ int MixedProcessedAudioStream::writeLastFrameRepeatedWithFade(int samples) { return deviceSamplesWritten; } -int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples) { +int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) { + QByteArray decodedBuffer; + if (_decoder) { + _decoder->decode(packetAfterStreamProperties, decodedBuffer); + } else { + decodedBuffer = packetAfterStreamProperties; + } - emit addedStereoSamples(packetAfterStreamProperties); + emit addedStereoSamples(decodedBuffer); QByteArray outputBuffer; - emit processSamples(packetAfterStreamProperties, outputBuffer); + emit processSamples(decodedBuffer, outputBuffer); _ringBuffer.writeData(outputBuffer.data(), outputBuffer.size()); diff --git a/libraries/audio/src/MixedProcessedAudioStream.h b/libraries/audio/src/MixedProcessedAudioStream.h index 5ea0157421..2f9a691278 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.h +++ b/libraries/audio/src/MixedProcessedAudioStream.h @@ -35,7 +35,7 @@ public: protected: int writeDroppableSilentSamples(int silentSamples); int writeLastFrameRepeatedWithFade(int samples); - int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples); + int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties); private: int networkToDeviceSamples(int networkSamples); diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 806e33819e..764ade2661 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -25,6 +25,8 @@ #include "AudioRingBuffer.h" #include "AudioLogging.h" +#include "AudioSRC.h" + #include "Sound.h" QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) { @@ -89,49 +91,22 @@ void Sound::downSample(const QByteArray& rawAudioByteArray) { // we want to convert it to the format that the audio-mixer wants // which is signed, 16-bit, 24Khz - int numSourceSamples = rawAudioByteArray.size() / sizeof(AudioConstants::AudioSample); + int numChannels = _isStereo ? 2 : 1; + AudioSRC resampler(48000, AudioConstants::SAMPLE_RATE, numChannels); - if (_isStereo && numSourceSamples % 2 != 0){ - // in the unlikely case that we have stereo audio but we seem to be missing a sample - // (the sample for one channel is missing in a set of interleaved samples) - // then drop the odd sample - --numSourceSamples; - } + // resize to max possible output + int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample)); + int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames); + int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample); + _byteArray.resize(maxDestinationBytes); - int numDestinationSamples = numSourceSamples / 2.0f; - - if (_isStereo && numDestinationSamples % 2 != 0) { - // if this is stereo we need to make sure we produce stereo output - // which means we should have an even number of output samples - numDestinationSamples += 1; - } - - int numDestinationBytes = numDestinationSamples * sizeof(AudioConstants::AudioSample); + int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(), + (int16_t*)_byteArray.data(), + numSourceFrames); + // truncate to actual output + int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample); _byteArray.resize(numDestinationBytes); - - int16_t* sourceSamples = (int16_t*) rawAudioByteArray.data(); - int16_t* destinationSamples = (int16_t*) _byteArray.data(); - - if (_isStereo) { - for (int i = 0; i < numSourceSamples; i += 4) { - if (i + 2 >= numSourceSamples) { - destinationSamples[i / 2] = sourceSamples[i]; - destinationSamples[(i / 2) + 1] = sourceSamples[i + 1]; - } else { - destinationSamples[i / 2] = (sourceSamples[i] + sourceSamples[i + 2]) / 2; - destinationSamples[(i / 2) + 1] = (sourceSamples[i + 1] + sourceSamples[i + 3]) / 2; - } - } - } else { - for (int i = 1; i < numSourceSamples; i += 2) { - if (i + 1 >= numSourceSamples) { - destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] + sourceSamples[i]) / 2; - } else { - destinationSamples[(i - 1) / 2] = ((sourceSamples[i - 1] + sourceSamples[i + 1]) / 4) + (sourceSamples[i] / 2); - } - } - } } // diff --git a/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp index 96a2cee204..6b34c68959 100644 --- a/libraries/audio/src/SoundCache.cpp +++ b/libraries/audio/src/SoundCache.cpp @@ -14,7 +14,7 @@ #include "AudioLogging.h" #include "SoundCache.h" -static int soundPointerMetaTypeId = qRegisterMetaType(); +int soundPointerMetaTypeId = qRegisterMetaType(); SoundCache::SoundCache(QObject* parent) : ResourceCache(parent) diff --git a/libraries/audio/src/avx2/AudioSRC_avx2.cpp b/libraries/audio/src/avx2/AudioSRC_avx2.cpp new file mode 100644 index 0000000000..e634554bfb --- /dev/null +++ b/libraries/audio/src/avx2/AudioSRC_avx2.cpp @@ -0,0 +1,201 @@ +// +// AudioSRC_avx2.cpp +// libraries/audio/src +// +// Created by Ken Cooke on 6/5/16. +// Copyright 2016 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 +// + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) + +#include +#include + +#include "../AudioSRC.h" + +#ifndef __AVX2__ +#error Must be compiled with /arch:AVX2 or -mavx2 -mfma. +#endif + +// high/low part of int64_t +#define LO32(a) ((uint32_t)(a)) +#define HI32(a) ((int32_t)((a) >> 32)) + +int AudioSRC::multirateFilter1_AVX2(const float* input0, float* output0, int inputFrames) { + int outputFrames = 0; + + assert(_numTaps % 8 == 0); // SIMD8 + + if (_step == 0) { // rational + + int32_t i = HI32(_offset); + + while (i < inputFrames) { + + const float* c0 = &_polyphaseFilter[_numTaps * _phase]; + + __m256 acc0 = _mm256_setzero_ps(); + + for (int j = 0; j < _numTaps; j += 8) { + + //float coef = c0[j]; + __m256 coef0 = _mm256_loadu_ps(&c0[j]); + + //acc += input[i + j] * coef; + acc0 = _mm256_fmadd_ps(_mm256_loadu_ps(&input0[i + j]), coef0, acc0); + } + + // horizontal sum + acc0 = _mm256_hadd_ps(acc0, acc0); + __m128 t0 = _mm_add_ps(_mm256_castps256_ps128(acc0), _mm256_extractf128_ps(acc0, 1)); + t0 = _mm_add_ps(t0, _mm_movehdup_ps(t0)); + + _mm_store_ss(&output0[outputFrames], t0); + outputFrames += 1; + + i += _stepTable[_phase]; + if (++_phase == _upFactor) { + _phase = 0; + } + } + _offset = (int64_t)(i - inputFrames) << 32; + + } else { // irrational + + while (HI32(_offset) < inputFrames) { + + int32_t i = HI32(_offset); + uint32_t f = LO32(_offset); + + uint32_t phase = f >> SRC_FRACBITS; + float ftmp = (f & SRC_FRACMASK) * QFRAC_TO_FLOAT; + + const float* c0 = &_polyphaseFilter[_numTaps * (phase + 0)]; + const float* c1 = &_polyphaseFilter[_numTaps * (phase + 1)]; + + __m256 acc0 = _mm256_setzero_ps(); + __m256 frac = _mm256_broadcast_ss(&ftmp); + + for (int j = 0; j < _numTaps; j += 8) { + + //float coef = c0[j] + frac * (c1[j] - c0[j]); + __m256 coef0 = _mm256_loadu_ps(&c0[j]); + __m256 coef1 = _mm256_loadu_ps(&c1[j]); + coef1 = _mm256_sub_ps(coef1, coef0); + coef0 = _mm256_fmadd_ps(coef1, frac, coef0); + + //acc += input[i + j] * coef; + acc0 = _mm256_fmadd_ps(_mm256_loadu_ps(&input0[i + j]), coef0, acc0); + } + + // horizontal sum + acc0 = _mm256_hadd_ps(acc0, acc0); + __m128 t0 = _mm_add_ps(_mm256_castps256_ps128(acc0), _mm256_extractf128_ps(acc0, 1)); + t0 = _mm_add_ps(t0, _mm_movehdup_ps(t0)); + + _mm_store_ss(&output0[outputFrames], t0); + outputFrames += 1; + + _offset += _step; + } + _offset -= (int64_t)inputFrames << 32; + } + _mm256_zeroupper(); + + return outputFrames; +} + +int AudioSRC::multirateFilter2_AVX2(const float* input0, const float* input1, float* output0, float* output1, int inputFrames) { + int outputFrames = 0; + + assert(_numTaps % 8 == 0); // SIMD8 + + if (_step == 0) { // rational + + int32_t i = HI32(_offset); + + while (i < inputFrames) { + + const float* c0 = &_polyphaseFilter[_numTaps * _phase]; + + __m256 acc0 = _mm256_setzero_ps(); + __m256 acc1 = _mm256_setzero_ps(); + + for (int j = 0; j < _numTaps; j += 8) { + + //float coef = c0[j]; + __m256 coef0 = _mm256_loadu_ps(&c0[j]); + + //acc += input[i + j] * coef; + acc0 = _mm256_fmadd_ps(_mm256_loadu_ps(&input0[i + j]), coef0, acc0); + acc1 = _mm256_fmadd_ps(_mm256_loadu_ps(&input1[i + j]), coef0, acc1); + } + + // horizontal sum + acc0 = _mm256_hadd_ps(acc0, acc1); + __m128 t0 = _mm_add_ps(_mm256_castps256_ps128(acc0), _mm256_extractf128_ps(acc0, 1)); + t0 = _mm_add_ps(t0, _mm_movehdup_ps(t0)); + + _mm_store_ss(&output0[outputFrames], t0); + _mm_store_ss(&output1[outputFrames], _mm_shuffle_ps(t0, t0, _MM_SHUFFLE(0,0,0,2))); + outputFrames += 1; + + i += _stepTable[_phase]; + if (++_phase == _upFactor) { + _phase = 0; + } + } + _offset = (int64_t)(i - inputFrames) << 32; + + } else { // irrational + + while (HI32(_offset) < inputFrames) { + + int32_t i = HI32(_offset); + uint32_t f = LO32(_offset); + + uint32_t phase = f >> SRC_FRACBITS; + float ftmp = (f & SRC_FRACMASK) * QFRAC_TO_FLOAT; + + const float* c0 = &_polyphaseFilter[_numTaps * (phase + 0)]; + const float* c1 = &_polyphaseFilter[_numTaps * (phase + 1)]; + + __m256 acc0 = _mm256_setzero_ps(); + __m256 acc1 = _mm256_setzero_ps(); + __m256 frac = _mm256_broadcast_ss(&ftmp); + + for (int j = 0; j < _numTaps; j += 8) { + + //float coef = c0[j] + frac * (c1[j] - c0[j]); + __m256 coef0 = _mm256_loadu_ps(&c0[j]); + __m256 coef1 = _mm256_loadu_ps(&c1[j]); + coef1 = _mm256_sub_ps(coef1, coef0); + coef0 = _mm256_fmadd_ps(coef1, frac, coef0); + + //acc += input[i + j] * coef; + acc0 = _mm256_fmadd_ps(_mm256_loadu_ps(&input0[i + j]), coef0, acc0); + acc1 = _mm256_fmadd_ps(_mm256_loadu_ps(&input1[i + j]), coef0, acc1); + } + + // horizontal sum + acc0 = _mm256_hadd_ps(acc0, acc1); + __m128 t0 = _mm_add_ps(_mm256_castps256_ps128(acc0), _mm256_extractf128_ps(acc0, 1)); + t0 = _mm_add_ps(t0, _mm_movehdup_ps(t0)); + + _mm_store_ss(&output0[outputFrames], t0); + _mm_store_ss(&output1[outputFrames], _mm_shuffle_ps(t0, t0, _MM_SHUFFLE(0,0,0,2))); + outputFrames += 1; + + _offset += _step; + } + _offset -= (int64_t)inputFrames << 32; + } + _mm256_zeroupper(); + + return outputFrames; +} + +#endif diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9c556dc42b..e73702cd95 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -34,9 +34,12 @@ #include #include #include +#include #include "AvatarLogging.h" +//#define WANT_DEBUG + quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND; using namespace std; @@ -46,6 +49,52 @@ const glm::vec3 DEFAULT_LOCAL_AABOX_SCALE(1.0f); const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData"; +namespace AvatarDataPacket { + // NOTE: AvatarDataPackets start with a uint16_t sequence number that is not reflected in the Header structure. + + PACKED_BEGIN struct Header { + float position[3]; // skeletal model's position + float globalPosition[3]; // avatar's position + uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to + uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag. + float lookAtPosition[3]; // world space position that eyes are focusing on. + float audioLoudness; // current loundess of microphone + uint8_t flags; + } PACKED_END; + const size_t HEADER_SIZE = 49; + + // only present if HAS_REFERENTIAL flag is set in header.flags + PACKED_BEGIN struct ParentInfo { + uint8_t parentUUID[16]; // rfc 4122 encoded + uint16_t parentJointIndex; + } PACKED_END; + const size_t PARENT_INFO_SIZE = 18; + + // only present if IS_FACESHIFT_CONNECTED flag is set in header.flags + PACKED_BEGIN struct FaceTrackerInfo { + float leftEyeBlink; + float rightEyeBlink; + float averageLoudness; + float browAudioLift; + uint8_t numBlendshapeCoefficients; + // float blendshapeCoefficients[numBlendshapeCoefficients]; + } PACKED_END; + const size_t FACE_TRACKER_INFO_SIZE = 17; + + // variable length structure follows + /* + struct JointData { + uint8_t numJoints; + uint8_t rotationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed rotation follows. + SixByteQuat rotation[numValidRotations]; // encodeded and compressed by packOrientationQuatToSixBytes() + uint8_t translationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed translation follows. + SixByteTrans translation[numValidTranslations]; // encodeded and compressed by packFloatVec3ToSignedTwoByteFixed() + }; + */ +} + +#define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0) + AvatarData::AvatarData() : SpatiallyNestable(NestableType::Avatar, QUuid()), _handPosition(0.0f), @@ -66,6 +115,10 @@ AvatarData::AvatarData() : setBodyPitch(0.0f); setBodyYaw(-90.0f); setBodyRoll(0.0f); + + ASSERT(sizeof(AvatarDataPacket::Header) == AvatarDataPacket::HEADER_SIZE); + ASSERT(sizeof(AvatarDataPacket::ParentInfo) == AvatarDataPacket::PARENT_INFO_SIZE); + ASSERT(sizeof(AvatarDataPacket::FaceTrackerInfo) == AvatarDataPacket::FACE_TRACKER_INFO_SIZE); } AvatarData::~AvatarData() { @@ -139,87 +192,70 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); unsigned char* startPosition = destinationBuffer; - const glm::vec3& position = getLocalPosition(); - memcpy(destinationBuffer, &position, sizeof(position)); - destinationBuffer += sizeof(position); + auto header = reinterpret_cast(destinationBuffer); + header->position[0] = getLocalPosition().x; + header->position[1] = getLocalPosition().y; + header->position[2] = getLocalPosition().z; + header->globalPosition[0] = _globalPosition.x; + header->globalPosition[1] = _globalPosition.y; + header->globalPosition[2] = _globalPosition.z; - 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); + packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 0), bodyEulerAngles.y); + packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 1), bodyEulerAngles.x); + packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 2), bodyEulerAngles.z); + packFloatRatioToTwoByte((uint8_t*)(&header->scale), _targetScale); + header->lookAtPosition[0] = _headData->_lookAtPosition.x; + header->lookAtPosition[1] = _headData->_lookAtPosition.y; + header->lookAtPosition[2] = _headData->_lookAtPosition.z; + header->audioLoudness = _headData->_audioLoudness; - // Body scale - destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _targetScale); - - // Lookat Position - memcpy(destinationBuffer, &_headData->_lookAtPosition, sizeof(_headData->_lookAtPosition)); - destinationBuffer += sizeof(_headData->_lookAtPosition); - - // Instantaneous audio loudness (used to drive facial animation) - memcpy(destinationBuffer, &_headData->_audioLoudness, sizeof(float)); - destinationBuffer += sizeof(float); - - // bitMask of less than byte wide items - unsigned char bitItems = 0; - - // key state - setSemiNibbleAt(bitItems,KEY_STATE_START_BIT,_keyState); + setSemiNibbleAt(header->flags, KEY_STATE_START_BIT, _keyState); // hand state bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG; - setSemiNibbleAt(bitItems, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); + setSemiNibbleAt(header->flags, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); if (isFingerPointing) { - setAtBit(bitItems, HAND_STATE_FINGER_POINTING_BIT); + setAtBit(header->flags, HAND_STATE_FINGER_POINTING_BIT); } // faceshift state if (_headData->_isFaceTrackerConnected) { - setAtBit(bitItems, IS_FACESHIFT_CONNECTED); + setAtBit(header->flags, IS_FACESHIFT_CONNECTED); } // eye tracker state if (_headData->_isEyeTrackerConnected) { - setAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); + setAtBit(header->flags, IS_EYE_TRACKER_CONNECTED); } // referential state QUuid parentID = getParentID(); if (!parentID.isNull()) { - setAtBit(bitItems, HAS_REFERENTIAL); + setAtBit(header->flags, HAS_REFERENTIAL); } - *destinationBuffer++ = bitItems; + destinationBuffer += sizeof(AvatarDataPacket::Header); if (!parentID.isNull()) { + auto parentInfo = reinterpret_cast(destinationBuffer); QByteArray referentialAsBytes = parentID.toRfc4122(); - memcpy(destinationBuffer, referentialAsBytes.data(), referentialAsBytes.size()); - destinationBuffer += referentialAsBytes.size(); - memcpy(destinationBuffer, &_parentJointIndex, sizeof(_parentJointIndex)); - destinationBuffer += sizeof(_parentJointIndex); + memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size()); + parentInfo->parentJointIndex = _parentJointIndex; + destinationBuffer += sizeof(AvatarDataPacket::ParentInfo); } // If it is connected, pack up the data if (_headData->_isFaceTrackerConnected) { - memcpy(destinationBuffer, &_headData->_leftEyeBlink, sizeof(float)); - destinationBuffer += sizeof(float); + auto faceTrackerInfo = reinterpret_cast(destinationBuffer); - memcpy(destinationBuffer, &_headData->_rightEyeBlink, sizeof(float)); - destinationBuffer += sizeof(float); + faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; + faceTrackerInfo->rightEyeBlink = _headData->_rightEyeBlink; + faceTrackerInfo->averageLoudness = _headData->_averageLoudness; + faceTrackerInfo->browAudioLift = _headData->_browAudioLift; + faceTrackerInfo->numBlendshapeCoefficients = _headData->_blendshapeCoefficients.size(); + destinationBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); - memcpy(destinationBuffer, &_headData->_averageLoudness, sizeof(float)); - destinationBuffer += sizeof(float); - - memcpy(destinationBuffer, &_headData->_browAudioLift, sizeof(float)); - destinationBuffer += sizeof(float); - - *destinationBuffer++ = _headData->_blendshapeCoefficients.size(); - memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), - _headData->_blendshapeCoefficients.size() * sizeof(float)); + // followed by a variable number of float coefficients + memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), _headData->_blendshapeCoefficients.size() * sizeof(float)); destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); } - // pupil dilation - destinationBuffer += packFloatToByte(destinationBuffer, _headData->_pupilDilation, 1.0f); - // joint rotation data *destinationBuffer++ = _jointData.size(); unsigned char* validityPosition = destinationBuffer; @@ -234,7 +270,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { _lastSentJointData.resize(_jointData.size()); for (int i=0; i < _jointData.size(); i++) { - const JointData& data = _jointData.at(i); + const JointData& data = _jointData[i]; if (sendAll || _lastSentJointData[i].rotation != data.rotation) { if (sendAll || !cullSmallChanges || @@ -259,9 +295,9 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { validityBit = 0; validity = *validityPosition++; for (int i = 0; i < _jointData.size(); i ++) { - const JointData& data = _jointData[ i ]; + const JointData& data = _jointData[i]; if (validity & (1 << validityBit)) { - destinationBuffer += packOrientationQuatToBytes(destinationBuffer, data.rotation); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); } if (++validityBit == BITS_IN_BYTE) { validityBit = 0; @@ -282,7 +318,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { float maxTranslationDimension = 0.0; for (int i=0; i < _jointData.size(); i++) { - const JointData& data = _jointData.at(i); + const JointData& data = _jointData[i]; if (sendAll || _lastSentJointData[i].translation != data.translation) { if (sendAll || !cullSmallChanges || @@ -304,23 +340,19 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { } } - if (validityBit != 0) { *destinationBuffer++ = validity; } - // TODO -- automatically pick translationCompressionRadix - int translationCompressionRadix = 12; - - *destinationBuffer++ = translationCompressionRadix; + const int TRANSLATION_COMPRESSION_RADIX = 12; validityBit = 0; validity = *validityPosition++; for (int i = 0; i < _jointData.size(); i ++) { - const JointData& data = _jointData[ i ]; + const JointData& data = _jointData[i]; if (validity & (1 << validityBit)) { destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, translationCompressionRadix); + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); } if (++validityBit == BITS_IN_BYTE) { validityBit = 0; @@ -333,7 +365,6 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { qDebug() << "AvatarData::toByteArray" << cullSmallChanges << sendAll << "rotations:" << rotationSentCount << "translations:" << translationSentCount << "largest:" << maxTranslationDimension - << "radix:" << translationCompressionRadix << "size:" << (beforeRotations - startPosition) << "+" << (beforeTranslations - beforeRotations) << "+" @@ -370,6 +401,12 @@ void AvatarData::doneEncoding(bool cullSmallChanges) { } bool AvatarData::shouldLogError(const quint64& now) { +#ifdef WANT_DEBUG + if (now > 0) { + return true; + } +#endif + if (now > _errorLogExpiry) { _errorLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; return true; @@ -377,134 +414,94 @@ bool AvatarData::shouldLogError(const quint64& now) { return false; } +#define PACKET_READ_CHECK(ITEM_NAME, SIZE_TO_READ) \ + if ((endPosition - sourceBuffer) < (int)SIZE_TO_READ) { \ + if (shouldLogError(now)) { \ + qCWarning(avatars) << "AvatarData packet too small, attempting to read " << \ + #ITEM_NAME << ", only " << (endPosition - sourceBuffer) << \ + " bytes left, " << getSessionUUID(); \ + } \ + return buffer.size(); \ + } + // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { - // lazily allocate memory for HeadData in case we're not an Avatar instance if (!_headData) { _headData = new HeadData(this); } const unsigned char* startPosition = reinterpret_cast(buffer.data()); + const unsigned char* endPosition = startPosition + buffer.size(); const unsigned char* sourceBuffer = startPosition; quint64 now = usecTimestampNow(); - // The absolute minimum size of the update data is as follows: - // 36 bytes of "plain old data" { - // position = 12 bytes - // bodyYaw = 2 (compressed float) - // bodyPitch = 2 (compressed float) - // bodyRoll = 2 (compressed float) - // targetScale = 2 (compressed float) - // lookAt = 12 - // audioLoudness = 4 - // } - // + 1 byte for varying data - // + 1 byte for pupilSize - // + 1 byte for numJoints (0) - // = 39 bytes - int minPossibleSize = 39; + PACKET_READ_CHECK(Header, sizeof(AvatarDataPacket::Header)); + auto header = reinterpret_cast(sourceBuffer); + sourceBuffer += sizeof(AvatarDataPacket::Header); - int maxAvailableSize = buffer.size(); - if (minPossibleSize > maxAvailableSize) { + glm::vec3 position = glm::vec3(header->position[0], header->position[1], header->position[2]); + _globalPosition = glm::vec3(header->globalPosition[0], header->globalPosition[1], header->globalPosition[2]); + if (isNaN(position)) { if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet at the start; " - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; + qCWarning(avatars) << "Discard AvatarData packet: position NaN, uuid " << getSessionUUID(); } - // this packet is malformed so we report all bytes as consumed - return maxAvailableSize; + return buffer.size(); + } + setLocalPosition(position); + + float pitch, yaw, roll; + unpackFloatAngleFromTwoByte(header->localOrientation + 0, &yaw); + unpackFloatAngleFromTwoByte(header->localOrientation + 1, &pitch); + unpackFloatAngleFromTwoByte(header->localOrientation + 2, &roll); + if (isNaN(yaw) || isNaN(pitch) || isNaN(roll)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: localOriention is NaN, uuid " << getSessionUUID(); + } + return buffer.size(); } - { // Body world position, rotation, and scale - // position - glm::vec3 position; - memcpy(&position, sourceBuffer, sizeof(position)); - sourceBuffer += sizeof(position); + glm::quat currentOrientation = getLocalOrientation(); + glm::vec3 newEulerAngles(pitch, yaw, roll); + glm::quat newOrientation = glm::quat(glm::radians(newEulerAngles)); + if (currentOrientation != newOrientation) { + _hasNewJointRotations = true; + setLocalOrientation(newOrientation); + } - 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; + float scale; + unpackFloatRatioFromTwoByte((uint8_t*)&header->scale, scale); + if (isNaN(scale)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: scale NaN, uuid " << getSessionUUID(); } - setLocalPosition(position); + return buffer.size(); + } + _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, scale)); - // rotation (NOTE: This needs to become a quaternion to save two bytes) - float yaw, pitch, roll; - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &yaw); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &pitch); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &roll); - if (glm::isnan(yaw) || glm::isnan(pitch) || glm::isnan(roll)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::yaw,pitch,roll; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; + glm::vec3 lookAt = glm::vec3(header->lookAtPosition[0], header->lookAtPosition[1], header->lookAtPosition[2]); + if (isNaN(lookAt)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: lookAtPosition is NaN, uuid " << getSessionUUID(); } + return buffer.size(); + } + _headData->_lookAtPosition = lookAt; - // 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. - glm::quat currentOrientation = getLocalOrientation(); - glm::vec3 newEulerAngles(pitch, yaw, roll); - glm::quat newOrientation = glm::quat(glm::radians(newEulerAngles)); - if (currentOrientation != newOrientation) { - _hasNewJointRotations = true; - setLocalOrientation(newOrientation); + float audioLoudness = header->audioLoudness; + if (isNaN(audioLoudness)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: audioLoudness is NaN, uuid " << getSessionUUID(); } - - // scale - float scale; - sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, scale); - if (glm::isnan(scale)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::scale; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, scale)); - } // 20 bytes - - { // Lookat Position - glm::vec3 lookAt; - memcpy(&lookAt, sourceBuffer, sizeof(lookAt)); - sourceBuffer += sizeof(lookAt); - if (glm::isnan(lookAt.x) || glm::isnan(lookAt.y) || glm::isnan(lookAt.z)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::lookAt; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _headData->_lookAtPosition = lookAt; - } // 12 bytes - - { // AudioLoudness - // Instantaneous audio loudness (used to drive facial animation) - float audioLoudness; - memcpy(&audioLoudness, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - if (glm::isnan(audioLoudness)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::audioLoudness; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _headData->_audioLoudness = audioLoudness; - } // 4 bytes + return buffer.size(); + } + _headData->_audioLoudness = audioLoudness; { // bitFlags and face data - unsigned char bitItems = *sourceBuffer++; + uint8_t bitItems = header->flags; // key state, stored as a semi-nibble in the bitItems - _keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT); + _keyState = (KeyState)getSemiNibbleAt(bitItems, KEY_STATE_START_BIT); // hand state, stored as a semi-nibble plus a bit in the bitItems // we store the hand state as well as other items in a shared bitset. The hand state is an octal, but is split @@ -521,98 +518,47 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { bool hasReferential = oneAtBit(bitItems, HAS_REFERENTIAL); if (hasReferential) { - const int sizeOfPackedUuid = 16; - QByteArray referentialAsBytes((const char*)sourceBuffer, sizeOfPackedUuid); - _parentID = QUuid::fromRfc4122(referentialAsBytes); - sourceBuffer += sizeOfPackedUuid; - memcpy(&_parentJointIndex, sourceBuffer, sizeof(_parentJointIndex)); - sourceBuffer += sizeof(_parentJointIndex); + PACKET_READ_CHECK(ParentInfo, sizeof(AvatarDataPacket::ParentInfo)); + auto parentInfo = reinterpret_cast(sourceBuffer); + sourceBuffer += sizeof(AvatarDataPacket::ParentInfo); + + QByteArray byteArray((const char*)parentInfo->parentUUID, NUM_BYTES_RFC4122_UUID); + _parentID = QUuid::fromRfc4122(byteArray); + _parentJointIndex = parentInfo->parentJointIndex; } else { _parentID = QUuid(); } if (_headData->_isFaceTrackerConnected) { - float leftEyeBlink, rightEyeBlink, averageLoudness, browAudioLift; - minPossibleSize += sizeof(leftEyeBlink) + sizeof(rightEyeBlink) + sizeof(averageLoudness) + sizeof(browAudioLift); - minPossibleSize++; // one byte for blendDataSize - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after BitItems;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; - } - // unpack face data - memcpy(&leftEyeBlink, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); + PACKET_READ_CHECK(FaceTrackerInfo, sizeof(AvatarDataPacket::FaceTrackerInfo)); + auto faceTrackerInfo = reinterpret_cast(sourceBuffer); + sourceBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); - memcpy(&rightEyeBlink, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); + _headData->_leftEyeBlink = faceTrackerInfo->leftEyeBlink; + _headData->_rightEyeBlink = faceTrackerInfo->rightEyeBlink; + _headData->_averageLoudness = faceTrackerInfo->averageLoudness; + _headData->_browAudioLift = faceTrackerInfo->browAudioLift; - memcpy(&averageLoudness, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - - memcpy(&browAudioLift, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - - if (glm::isnan(leftEyeBlink) || glm::isnan(rightEyeBlink) - || glm::isnan(averageLoudness) || glm::isnan(browAudioLift)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::faceData; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _headData->_leftEyeBlink = leftEyeBlink; - _headData->_rightEyeBlink = rightEyeBlink; - _headData->_averageLoudness = averageLoudness; - _headData->_browAudioLift = browAudioLift; - - int numCoefficients = (int)(*sourceBuffer++); - int blendDataSize = numCoefficients * sizeof(float); - minPossibleSize += blendDataSize; - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after Blendshapes;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; - } - - _headData->_blendshapeCoefficients.resize(numCoefficients); - memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, blendDataSize); - sourceBuffer += numCoefficients * sizeof(float); - - //bitItemsDataSize = 4 * sizeof(float) + 1 + blendDataSize; + int numCoefficients = faceTrackerInfo->numBlendshapeCoefficients; + const int coefficientsSize = sizeof(float) * numCoefficients; + PACKET_READ_CHECK(FaceTrackerCoefficients, coefficientsSize); + _headData->_blendshapeCoefficients.resize(numCoefficients); // make sure there's room for the copy! + memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, coefficientsSize); + sourceBuffer += coefficientsSize; } - } // 1 + bitItemsDataSize bytes - - { // pupil dilation - sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f); - } // 1 byte - - // joint rotations - int numJoints = *sourceBuffer++; - int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); - minPossibleSize += bytesOfValidity; - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after JointValidityBits;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; } - int numValidJointRotations = 0; + + PACKET_READ_CHECK(NumJoints, sizeof(uint8_t)); + int numJoints = *sourceBuffer++; + _jointData.resize(numJoints); + const int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); + PACKET_READ_CHECK(JointRotationValidityBits, bytesOfValidity); + + int numValidJointRotations = 0; QVector validRotations; validRotations.resize(numJoints); - { // rotation validity bits unsigned char validity = 0; int validityBit = 0; @@ -627,38 +573,26 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { validRotations[i] = valid; validityBit = (validityBit + 1) % BITS_IN_BYTE; } - } // 1 + bytesOfValidity bytes - - // each joint rotation component is stored in two bytes (sizeof(uint16_t)) - int COMPONENTS_PER_QUATERNION = 4; - minPossibleSize += numValidJointRotations * COMPONENTS_PER_QUATERNION * sizeof(uint16_t); - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after JointData rotation validity;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; } - { // joint data - for (int i = 0; i < numJoints; i++) { - JointData& data = _jointData[i]; - if (validRotations[i]) { - _hasNewJointRotations = true; - data.rotationSet = true; - sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation); - } + // each joint rotation is stored in 6 bytes. + const int COMPRESSED_QUATERNION_SIZE = 6; + PACKET_READ_CHECK(JointRotations, numValidJointRotations * COMPRESSED_QUATERNION_SIZE); + for (int i = 0; i < numJoints; i++) { + JointData& data = _jointData[i]; + if (validRotations[i]) { + sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation); + _hasNewJointRotations = true; + data.rotationSet = true; } - } // numJoints * 8 bytes + } + + PACKET_READ_CHECK(JointTranslationValidityBits, bytesOfValidity); - // joint translations // get translation validity bits -- these indicate which translations were packed int numValidJointTranslations = 0; QVector validTranslations; validTranslations.resize(numJoints); - { // translation validity bits unsigned char validity = 0; int validityBit = 0; @@ -675,37 +609,24 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } } // 1 + bytesOfValidity bytes - // each joint translation component is stored in 6 bytes. 1 byte for translationCompressionRadix - minPossibleSize += numValidJointTranslations * 6 + 1; - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after JointData translation validity;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; + // each joint translation component is stored in 6 bytes. + const int COMPRESSED_TRANSLATION_SIZE = 6; + PACKET_READ_CHECK(JointTranslation, numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE); + const int TRANSLATION_COMPRESSION_RADIX = 12; + + for (int i = 0; i < numJoints; i++) { + JointData& data = _jointData[i]; + if (validTranslations[i]) { + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + _hasNewJointTranslations = true; + data.translationSet = true; } - return maxAvailableSize; } - int translationCompressionRadix = *sourceBuffer++; - - { // joint data - for (int i = 0; i < numJoints; i++) { - JointData& data = _jointData[i]; - if (validTranslations[i]) { - sourceBuffer += - unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, translationCompressionRadix); - _hasNewJointTranslations = true; - data.translationSet = true; - } - } - } // numJoints * 12 bytes - #ifdef WANT_DEBUG if (numValidJointRotations > 15) { qDebug() << "RECEIVING -- rotations:" << numValidJointRotations << "translations:" << numValidJointTranslations - << "radix:" << translationCompressionRadix << "size:" << (int)(sourceBuffer - startPosition); } #endif @@ -748,7 +669,9 @@ void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::v } JointData& data = _jointData[index]; data.rotation = rotation; + data.rotationSet = true; data.translation = translation; + data.translationSet = true; } void AvatarData::clearJointData(int index) { @@ -853,6 +776,7 @@ void AvatarData::setJointRotation(int index, const glm::quat& rotation) { } JointData& data = _jointData[index]; data.rotation = rotation; + data.rotationSet = true; } void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { @@ -868,6 +792,7 @@ void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { } JointData& data = _jointData[index]; data.translation = translation; + data.translationSet = true; } void AvatarData::clearJointData(const QString& name) { @@ -937,7 +862,6 @@ void AvatarData::setJointTranslations(QVector jointTranslations) { "setJointTranslations", Qt::BlockingQueuedConnection, Q_ARG(QVector, jointTranslations)); } - if (_jointData.size() < jointTranslations.size()) { _jointData.resize(jointTranslations.size()); } @@ -954,31 +878,37 @@ void AvatarData::clearJointsData() { } } -bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) { +void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) { QDataStream packetStream(data); - QUuid avatarUUID; - QUrl unusedModelURL; // legacy faceModel support - QUrl skeletonModelURL; - QVector attachmentData; - QString displayName; - packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName; + packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.avatarEntityData; +} +bool AvatarData::processAvatarIdentity(const Identity& identity) { bool hasIdentityChanged = false; - if (_firstSkeletonCheck || (skeletonModelURL != _skeletonModelURL)) { - setSkeletonModelURL(skeletonModelURL); + if (_firstSkeletonCheck || (identity.skeletonModelURL != _skeletonModelURL)) { + setSkeletonModelURL(identity.skeletonModelURL); hasIdentityChanged = true; _firstSkeletonCheck = false; } - if (displayName != _displayName) { - setDisplayName(displayName); + if (identity.displayName != _displayName) { + setDisplayName(identity.displayName); hasIdentityChanged = true; } - if (attachmentData != _attachmentData) { - setAttachmentData(attachmentData); + if (identity.attachmentData != _attachmentData) { + setAttachmentData(identity.attachmentData); + hasIdentityChanged = true; + } + + bool avatarEntityDataChanged = false; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityDataChanged = (identity.avatarEntityData != _avatarEntityData); + }); + if (avatarEntityDataChanged) { + setAvatarEntityData(identity.avatarEntityData); hasIdentityChanged = true; } @@ -991,21 +921,20 @@ QByteArray AvatarData::identityByteArray() { QUrl emptyURL(""); const QUrl& urlToSend = _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; - QUrl unusedModelURL; // legacy faceModel support - - identityStream << QUuid() << unusedModelURL << urlToSend << _attachmentData << _displayName; + _avatarEntitiesLock.withReadLock([&] { + identityStream << getSessionUUID() << urlToSend << _attachmentData << _displayName << _avatarEntityData; + }); return identityData; } - void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL; if (expanded == _skeletonModelURL) { return; } _skeletonModelURL = expanded; - qCDebug(avatars) << "Changing skeleton model for avatar to" << _skeletonModelURL.toString(); + qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString(); updateJointMappings(); } @@ -1167,11 +1096,14 @@ void AvatarData::sendIdentityPacket() { [&](const SharedNodePointer& node) { nodeList->sendPacketList(std::move(packetList), *node); }); + + _avatarEntityDataLocallyEdited = false; } void AvatarData::updateJointMappings() { _jointIndices.clear(); _jointNames.clear(); + _jointData.clear(); if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); @@ -1339,7 +1271,12 @@ static const QString JSON_AVATAR_HEAD_MODEL = QStringLiteral("headModel"); static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel"); static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName"); static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments"); +static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities"); static const QString JSON_AVATAR_SCALE = QStringLiteral("scale"); +static const QString JSON_AVATAR_VERSION = QStringLiteral("version"); + +static const int JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION = 0; +static const int JSON_AVATAR_JOINT_ROTATIONS_IN_ABSOLUTE_FRAME_VERSION = 1; QJsonValue toJsonValue(const JointData& joint) { QJsonArray result; @@ -1363,6 +1300,8 @@ JointData jointDataFromJsonValue(const QJsonValue& json) { QJsonObject AvatarData::toJson() const { QJsonObject root; + root[JSON_AVATAR_VERSION] = JSON_AVATAR_JOINT_ROTATIONS_IN_ABSOLUTE_FRAME_VERSION; + if (!getSkeletonModelURL().isEmpty()) { root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString(); } @@ -1377,6 +1316,19 @@ QJsonObject AvatarData::toJson() const { root[JSON_AVATAR_ATTACHEMENTS] = attachmentsJson; } + _avatarEntitiesLock.withReadLock([&] { + if (!_avatarEntityData.empty()) { + QJsonArray avatarEntityJson; + for (auto entityID : _avatarEntityData.keys()) { + QVariantMap entityData; + entityData.insert("id", entityID); + entityData.insert("properties", _avatarEntityData.value(entityID)); + avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); + } + root[JSON_AVATAR_ENTITIES] = avatarEntityJson; + } + }); + auto recordingBasis = getRecordingBasis(); bool success; Transform avatarTransform = getTransform(success); @@ -1418,6 +1370,15 @@ QJsonObject AvatarData::toJson() const { } void AvatarData::fromJson(const QJsonObject& json) { + + int version; + if (json.contains(JSON_AVATAR_VERSION)) { + version = json[JSON_AVATAR_VERSION].toInt(); + } else { + // initial data did not have a version field. + version = JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION; + } + // The head setOrientation likes to overwrite the avatar orientation, // so lets do the head first // Most head data is relative to the avatar, and needs no basis correction, @@ -1476,20 +1437,34 @@ void AvatarData::fromJson(const QJsonObject& json) { setAttachmentData(attachments); } - // Joint rotations are relative to the avatar, so they require no basis correction + // if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) { + // QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHEMENTS].toArray(); + // for (auto attachmentJson : attachmentsJson) { + // // TODO -- something + // } + // } + if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { - QVector jointArray; - QJsonArray jointArrayJson = json[JSON_AVATAR_JOINT_ARRAY].toArray(); - jointArray.reserve(jointArrayJson.size()); - int i = 0; - for (const auto& jointJson : jointArrayJson) { - auto joint = jointDataFromJsonValue(jointJson); - jointArray.push_back(joint); - setJointData(i, joint.rotation, joint.translation); - _jointData[i].rotationSet = true; // Have to do that to broadcast the avatar new pose - i++; + if (version == JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION) { + // because we don't have the full joint hierarchy skeleton of the model, + // we can't properly convert from relative rotations into absolute rotations. + quint64 now = usecTimestampNow(); + if (shouldLogError(now)) { + qCWarning(avatars) << "Version 0 avatar recordings not supported. using default rotations"; + } + } else { + QVector jointArray; + QJsonArray jointArrayJson = json[JSON_AVATAR_JOINT_ARRAY].toArray(); + jointArray.reserve(jointArrayJson.size()); + int i = 0; + for (const auto& jointJson : jointArrayJson) { + auto joint = jointDataFromJsonValue(jointJson); + jointArray.push_back(joint); + setJointData(i, joint.rotation, joint.translation); + i++; + } + setRawJointData(jointArray); } - setRawJointData(jointArray); } } @@ -1628,9 +1603,104 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) { QVector newAttachments; newAttachments.reserve(variant.size()); for (const auto& attachmentVar : variant) { - AttachmentData attachment; + AttachmentData attachment; attachment.fromVariant(attachmentVar); newAttachments.append(attachment); } setAttachmentData(newAttachments); } + +void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "updateAvatarEntity", Q_ARG(const QUuid&, entityID), Q_ARG(QByteArray, entityData)); + return; + } + _avatarEntitiesLock.withWriteLock([&] { + _avatarEntityData.insert(entityID, entityData); + _avatarEntityDataLocallyEdited = true; + }); +} + +void AvatarData::clearAvatarEntity(const QUuid& entityID) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "clearAvatarEntity", Q_ARG(const QUuid&, entityID)); + return; + } + + _avatarEntitiesLock.withWriteLock([&] { + _avatarEntityData.remove(entityID); + _avatarEntityDataLocallyEdited = true; + }); +} + +AvatarEntityMap AvatarData::getAvatarEntityData() const { + AvatarEntityMap result; + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(const_cast(this), "getAvatarEntityData", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(AvatarEntityMap, result)); + return result; + } + + _avatarEntitiesLock.withReadLock([&] { + result = _avatarEntityData; + }); + return result; +} + +void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setAvatarEntityData", Q_ARG(const AvatarEntityMap&, avatarEntityData)); + return; + } + _avatarEntitiesLock.withWriteLock([&] { + if (_avatarEntityData != avatarEntityData) { + // keep track of entities that were attached to this avatar but no longer are + AvatarEntityIDs previousAvatarEntityIDs = QSet::fromList(_avatarEntityData.keys()); + + _avatarEntityData = avatarEntityData; + setAvatarEntityDataChanged(true); + + foreach (auto entityID, previousAvatarEntityIDs) { + if (!_avatarEntityData.contains(entityID)) { + _avatarEntityDetached.insert(entityID); + } + } + } + }); +} + +AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() { + AvatarEntityIDs result; + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(const_cast(this), "getRecentlyDetachedIDs", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(AvatarEntityIDs, result)); + return result; + } + _avatarEntitiesLock.withWriteLock([&] { + result = _avatarEntityDetached; + _avatarEntityDetached.clear(); + }); + return result; +} + +QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) { + QScriptValue obj = engine->newObject(); + obj.setProperty("intersects", value.intersects); + QScriptValue avatarIDValue = quuidToScriptValue(engine, value.avatarID); + obj.setProperty("avatarID", avatarIDValue); + obj.setProperty("distance", value.distance); + QScriptValue intersection = vec3toScriptValue(engine, value.intersection); + obj.setProperty("intersection", intersection); + return obj; +} + +void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& value) { + value.intersects = object.property("intersects").toVariant().toBool(); + QScriptValue avatarIDValue = object.property("avatarID"); + quuidFromScriptValue(avatarIDValue, value.avatarID); + value.distance = object.property("distance").toVariant().toFloat(); + QScriptValue intersection = object.property("intersection"); + if (intersection.isValid()) { + vec3FromScriptValue(intersection, value.intersection); + } +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index e9dc100a7c..14b4f07471 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -53,6 +53,7 @@ typedef unsigned long long quint64; #include #include #include +#include #include "AABox.h" #include "HeadData.h" @@ -61,6 +62,8 @@ typedef unsigned long long quint64; using AvatarSharedPointer = std::shared_ptr; using AvatarWeakPointer = std::weak_ptr; using AvatarHash = QHash; +using AvatarEntityMap = QMap; +using AvatarEntityIDs = QSet; using AvatarDataSequenceNumber = uint16_t; @@ -134,6 +137,10 @@ class AttachmentData; class Transform; using TransformPointer = std::shared_ptr; +// When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows +// the value to be reset when the sessionID changes. +const QUuid AVATAR_SELF_ID = QUuid("{00000000-0000-0000-0000-000000000001}"); + class AvatarData : public QObject, public SpatiallyNestable { Q_OBJECT @@ -165,6 +172,7 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) public: + static const QString FRAME_NAME; static void fromFrame(const QByteArray& frameData, AvatarData& avatar); @@ -272,6 +280,9 @@ public: Q_INVOKABLE QVariantList getAttachmentsVariant() const; Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant); + Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); + Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID); + void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; } // key state @@ -280,7 +291,19 @@ public: const HeadData* getHeadData() const { return _headData; } - bool hasIdentityChangedAfterParsing(const QByteArray& data); + struct Identity { + QUuid uuid; + QUrl skeletonModelURL; + QVector attachmentData; + QString displayName; + AvatarEntityMap avatarEntityData; + }; + + static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut); + + // returns true if identity has changed, false otherwise. + bool processAvatarIdentity(const Identity& identity); + QByteArray identityByteArray(); const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } @@ -323,6 +346,11 @@ public: glm::vec3 getClientGlobalPosition() { return _globalPosition; } + Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const; + Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); + void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } + AvatarEntityIDs getAndClearRecentlyDetachedIDs(); + public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); @@ -390,6 +418,12 @@ protected: // updates about one avatar to another. glm::vec3 _globalPosition; + mutable ReadWriteLockable _avatarEntitiesLock; + AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar + AvatarEntityMap _avatarEntityData; + bool _avatarEntityDataLocallyEdited { false }; + bool _avatarEntityDataChanged { false }; + private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); static QUrl _defaultFullAvatarModelUrl; @@ -461,4 +495,19 @@ public: void registerAvatarTypes(QScriptEngine* engine); +class RayToAvatarIntersectionResult { +public: +RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {} + bool intersects; + QUuid avatarID; + float distance; + glm::vec3 intersection; +}; + +Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) + +QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results); +void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results); + + #endif // hifi_AvatarData_h diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index f14e2b0ad3..48b701d142 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -19,7 +19,9 @@ #include "AvatarHashMap.h" AvatarHashMap::AvatarHashMap() { - connect(DependencyManager::get().data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); + auto nodeList = DependencyManager::get(); + + connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); } QVector AvatarHashMap::getAvatarIdentifiers() { @@ -44,32 +46,46 @@ bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range return false; } +int AvatarHashMap::numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters) { + auto hashCopy = getHashCopy(); + auto rangeMeters2 = rangeMeters * rangeMeters; + int count = 0; + for (const AvatarSharedPointer& sharedAvatar : hashCopy) { + glm::vec3 avatarPosition = sharedAvatar->getPosition(); + auto distance2 = glm::distance2(avatarPosition, position); + if (distance2 < rangeMeters2) { + ++count; + } + } + return count; +} + AvatarSharedPointer AvatarHashMap::newSharedAvatar() { return std::make_shared(); } AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; - + auto avatar = newSharedAvatar(); avatar->setSessionUUID(sessionUUID); avatar->setOwningAvatarMixer(mixerWeakPointer); - + _avatarHash.insert(sessionUUID, avatar); emit avatarAddedEvent(sessionUUID); - + return avatar; } AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { QWriteLocker locker(&_hashLock); - + auto avatar = _avatarHash.value(sessionUUID); - + if (!avatar) { avatar = addAvatar(sessionUUID, mixerWeakPointer); } - + return avatar; } @@ -86,14 +102,17 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer mess // only add them if mixerWeakPointer points to something (meaning that mixer is still around) while (message->getBytesLeftToRead()) { QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); - + int positionBeforeRead = message->getPosition(); QByteArray byteArray = message->readWithoutCopy(message->getBytesLeftToRead()); - - if (sessionUUID != _lastOwnerSessionUUID) { + + // make sure this isn't our own avatar data or for a previously ignored node + auto nodeList = DependencyManager::get(); + + if (sessionUUID != _lastOwnerSessionUUID && !nodeList->isIgnoringNode(sessionUUID)) { auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); - + // have the matching (or new) avatar parse the data from the packet int bytesRead = avatar->parseDataFromBuffer(byteArray); message->seek(positionBeforeRead + bytesRead); @@ -107,32 +126,15 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer mess } void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode) { - // setup a data stream to parse the packet - QDataStream identityStream(message->getMessage()); - - QUuid sessionUUID; - - while (!identityStream.atEnd()) { - - QUrl faceMeshURL, skeletonURL; - QVector attachmentData; - QString displayName; - identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName; + AvatarData::Identity identity; + AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity); + // make sure this isn't for an ignored avatar + auto nodeList = DependencyManager::get(); + if (!nodeList->isIgnoringNode(identity.uuid)) { // mesh URL for a UUID, find avatar in our list - auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); - - if (avatar->getSkeletonModelURL().isEmpty() || (avatar->getSkeletonModelURL() != skeletonURL)) { - avatar->setSkeletonModelURL(skeletonURL); // Will expand "" to default and so will not continuously fire - } - - if (avatar->getAttachmentData() != attachmentData) { - avatar->setAttachmentData(attachmentData); - } - - if (avatar->getDisplayName() != displayName) { - avatar->setDisplayName(displayName); - } + auto avatar = newOrExistingAvatar(identity.uuid, sendingNode); + avatar->processAvatarIdentity(identity); } } @@ -144,9 +146,9 @@ void AvatarHashMap::processKillAvatar(QSharedPointer message, S void AvatarHashMap::removeAvatar(const QUuid& sessionUUID) { QWriteLocker locker(&_hashLock); - + auto removedAvatar = _avatarHash.take(sessionUUID); - + if (removedAvatar) { handleRemovedAvatar(removedAvatar); } diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 5f58074427..a59cc4fa96 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -19,12 +19,14 @@ #include #include +#include + #include #include #include #include "AvatarData.h" -#include + class AvatarHashMap : public QObject, public Dependency { Q_OBJECT @@ -39,6 +41,7 @@ public: Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID); virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) { return findAvatar(sessionID); } + int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters); signals: void avatarAddedEvent(const QUuid& sessionUUID); diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index b98112d6e0..72516d9740 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -31,9 +31,6 @@ HeadData::HeadData(AvatarData* owningAvatar) : _baseYaw(0.0f), _basePitch(0.0f), _baseRoll(0.0f), - _leanSideways(0.0f), - _leanForward(0.0f), - _torsoTwist(0.0f), _lookAtPosition(0.0f, 0.0f, 0.0f), _audioLoudness(0.0f), _isFaceTrackerConnected(false), @@ -43,10 +40,9 @@ HeadData::HeadData(AvatarData* owningAvatar) : _averageLoudness(0.0f), _browAudioLift(0.0f), _audioAverageLoudness(0.0f), - _pupilDilation(0.0f), _owningAvatar(owningAvatar) { - + } glm::quat HeadData::getRawOrientation() const { @@ -72,7 +68,7 @@ void HeadData::setOrientation(const glm::quat& orientation) { glm::vec3 newFront = glm::inverse(bodyOrientation) * (orientation * IDENTITY_FRONT); bodyOrientation = bodyOrientation * glm::angleAxis(atan2f(-newFront.x, -newFront.z), glm::vec3(0.0f, 1.0f, 0.0f)); _owningAvatar->setOrientation(bodyOrientation); - + // the rest goes to the head glm::vec3 eulers = glm::degrees(safeEulerAngles(glm::inverse(bodyOrientation) * orientation)); _basePitch = eulers.x; @@ -133,12 +129,6 @@ QJsonObject HeadData::toJson() const { if (getRawOrientation() != quat()) { headJson[JSON_AVATAR_HEAD_ROTATION] = toJsonValue(getRawOrientation()); } - if (getLeanForward() != 0.0f) { - headJson[JSON_AVATAR_HEAD_LEAN_FORWARD] = getLeanForward(); - } - if (getLeanSideways() != 0.0f) { - headJson[JSON_AVATAR_HEAD_LEAN_SIDEWAYS] = getLeanSideways(); - } auto lookat = getLookAtPosition(); if (lookat != vec3()) { vec3 relativeLookAt = glm::inverse(_owningAvatar->getOrientation()) * @@ -172,12 +162,6 @@ void HeadData::fromJson(const QJsonObject& json) { if (json.contains(JSON_AVATAR_HEAD_ROTATION)) { setOrientation(quatFromJsonValue(json[JSON_AVATAR_HEAD_ROTATION])); } - if (json.contains(JSON_AVATAR_HEAD_LEAN_FORWARD)) { - setLeanForward((float)json[JSON_AVATAR_HEAD_LEAN_FORWARD].toDouble()); - } - if (json.contains(JSON_AVATAR_HEAD_LEAN_SIDEWAYS)) { - setLeanSideways((float)json[JSON_AVATAR_HEAD_LEAN_SIDEWAYS].toDouble()); - } if (json.contains(JSON_AVATAR_HEAD_LOOKAT)) { auto relativeLookAt = vec3FromJsonValue(json[JSON_AVATAR_HEAD_LOOKAT]); @@ -186,4 +170,3 @@ void HeadData::fromJson(const QJsonObject& json) { } } } - diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index fef77c6f8f..af657339ba 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -34,7 +34,7 @@ class HeadData { public: explicit HeadData(AvatarData* owningAvatar); virtual ~HeadData() { }; - + // degrees float getBaseYaw() const { return _baseYaw; } void setBaseYaw(float yaw) { _baseYaw = glm::clamp(yaw, MIN_HEAD_YAW, MAX_HEAD_YAW); } @@ -42,7 +42,7 @@ public: void setBasePitch(float pitch) { _basePitch = glm::clamp(pitch, MIN_HEAD_PITCH, MAX_HEAD_PITCH); } float getBaseRoll() const { return _baseRoll; } void setBaseRoll(float roll) { _baseRoll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); } - + virtual void setFinalYaw(float finalYaw) { _baseYaw = finalYaw; } virtual void setFinalPitch(float finalPitch) { _basePitch = finalPitch; } virtual void setFinalRoll(float finalRoll) { _baseRoll = finalRoll; } @@ -64,26 +64,12 @@ public: void setBlendshape(QString name, float val); const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } void setBlendshapeCoefficients(const QVector& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; } - - float getPupilDilation() const { return _pupilDilation; } - void setPupilDilation(float pupilDilation) { _pupilDilation = pupilDilation; } - + const glm::vec3& getLookAtPosition() const { return _lookAtPosition; } void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; } - - - float getLeanSideways() const { return _leanSideways; } - float getLeanForward() const { return _leanForward; } - float getTorsoTwist() const { return _torsoTwist; } - virtual float getFinalLeanSideways() const { return _leanSideways; } - virtual float getFinalLeanForward() const { return _leanForward; } - - void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; } - void setLeanForward(float leanForward) { _leanForward = leanForward; } - void setTorsoTwist(float torsoTwist) { _torsoTwist = torsoTwist; } - + friend class AvatarData; - + QJsonObject toJson() const; void fromJson(const QJsonObject& json); @@ -92,9 +78,6 @@ protected: float _baseYaw; float _basePitch; float _baseRoll; - float _leanSideways; - float _leanForward; - float _torsoTwist; glm::vec3 _lookAtPosition; float _audioLoudness; @@ -106,9 +89,8 @@ protected: float _browAudioLift; float _audioAverageLoudness; QVector _blendshapeCoefficients; - float _pupilDilation; AvatarData* _owningAvatar; - + private: // privatize copy ctor and assignment operator so copies of this object cannot be made HeadData(const HeadData&); diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index dba856cbaa..79ff4ecbf8 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -121,16 +121,8 @@ namespace controller { return availableInputs; } - void ActionsDevice::update(float deltaTime, const InputCalibrationData& inpuCalibrationData, bool jointsCaptured) { - } - - void ActionsDevice::focusOutEvent() { - } - ActionsDevice::ActionsDevice() : InputDevice("Actions") { _deviceID = UserInputMapper::ACTIONS_DEVICE; } - ActionsDevice::~ActionsDevice() {} - } diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index efdc45cb3d..724d17d951 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -109,13 +109,11 @@ class ActionsDevice : public QObject, public InputDevice { Q_PROPERTY(QString name READ getName) public: - virtual EndpointPointer createEndpoint(const Input& input) const override; - virtual Input::NamedVector getAvailableInputs() const override; - virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; - virtual void focusOutEvent() override; - ActionsDevice(); - virtual ~ActionsDevice(); + + EndpointPointer createEndpoint(const Input& input) const override; + Input::NamedVector getAvailableInputs() const override; + }; } diff --git a/libraries/controllers/src/controllers/Forward.h b/libraries/controllers/src/controllers/Forward.h index e1a62556d4..23dd162831 100644 --- a/libraries/controllers/src/controllers/Forward.h +++ b/libraries/controllers/src/controllers/Forward.h @@ -32,6 +32,7 @@ class Mapping; using MappingPointer = std::shared_ptr; using MappingList = std::list; +struct Pose; } #endif diff --git a/libraries/controllers/src/controllers/InputDevice.h b/libraries/controllers/src/controllers/InputDevice.h index 93247965bc..10e1a104f4 100644 --- a/libraries/controllers/src/controllers/InputDevice.h +++ b/libraries/controllers/src/controllers/InputDevice.h @@ -31,6 +31,12 @@ namespace controller { class Endpoint; using EndpointPointer = std::shared_ptr; +enum Hand { + LEFT = 0, + RIGHT, + BOTH +}; + // NOTE: If something inherits from both InputDevice and InputPlugin, InputPlugin must go first. // e.g. class Example : public InputPlugin, public InputDevice // instead of class Example : public InputDevice, public InputPlugin @@ -55,11 +61,14 @@ public: const QString& getName() const { return _name; } + // By default, Input Devices do not support haptics + virtual bool triggerHapticPulse(float strength, float duration, controller::Hand hand) { return false; } + // Update call MUST be called once per simulation loop // It takes care of updating the action states and deltas - virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) = 0; + virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData) {}; - virtual void focusOutEvent() = 0; + virtual void focusOutEvent() {}; int getDeviceID() { return _deviceID; } void setDeviceID(int deviceID) { _deviceID = deviceID; } diff --git a/libraries/controllers/src/controllers/ScriptingInterface.cpp b/libraries/controllers/src/controllers/ScriptingInterface.cpp index c2e64ca19e..78e9378c18 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -140,6 +140,24 @@ namespace controller { return DependencyManager::get()->getActionNames(); } + bool ScriptingInterface::triggerHapticPulse(float strength, float duration, controller::Hand hand) const { + return DependencyManager::get()->triggerHapticPulse(strength, duration, hand); + } + + bool ScriptingInterface::triggerShortHapticPulse(float strength, controller::Hand hand) const { + const float SHORT_HAPTIC_DURATION_MS = 250.0f; + return DependencyManager::get()->triggerHapticPulse(strength, SHORT_HAPTIC_DURATION_MS, hand); + } + + bool ScriptingInterface::triggerHapticPulseOnDevice(unsigned int device, float strength, float duration, controller::Hand hand) const { + return DependencyManager::get()->triggerHapticPulseOnDevice(device, strength, duration, hand); + } + + bool ScriptingInterface::triggerShortHapticPulseOnDevice(unsigned int device, float strength, controller::Hand hand) const { + const float SHORT_HAPTIC_DURATION_MS = 250.0f; + return DependencyManager::get()->triggerHapticPulseOnDevice(device, strength, SHORT_HAPTIC_DURATION_MS, hand); + } + void ScriptingInterface::updateMaps() { QVariantMap newHardware; auto userInputMapper = DependencyManager::get(); diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index f30212a09e..713e864561 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -84,6 +84,11 @@ namespace controller { Q_INVOKABLE Pose getPoseValue(const int& source) const; Q_INVOKABLE Pose getPoseValue(StandardPoseChannel source, uint16_t device = 0) const; + Q_INVOKABLE bool triggerHapticPulse(float strength, float duration, controller::Hand hand = BOTH) const; + Q_INVOKABLE bool triggerShortHapticPulse(float strength, controller::Hand hand = BOTH) const; + Q_INVOKABLE bool triggerHapticPulseOnDevice(unsigned int device, float strength, float duration, controller::Hand hand = BOTH) const; + Q_INVOKABLE bool triggerShortHapticPulseOnDevice(unsigned int device, float strength, controller::Hand hand = BOTH) const; + Q_INVOKABLE QObject* newMapping(const QString& mappingName = QUuid::createUuid().toString()); Q_INVOKABLE void enableMapping(const QString& mappingName, bool enable = true); Q_INVOKABLE void disableMapping(const QString& mappingName) { enableMapping(mappingName, false); } diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index 5996cad5df..02ae5706b7 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -22,12 +22,6 @@ StandardController::StandardController() : InputDevice("Standard") { _deviceID = UserInputMapper::STANDARD_DEVICE; } -StandardController::~StandardController() { -} - -void StandardController::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { -} - void StandardController::focusOutEvent() { _axisStateMap.clear(); _buttonPressedMap.clear(); @@ -71,6 +65,8 @@ Input::NamedVector StandardController::getAvailableInputs() const { // Triggers makePair(LT, "LT"), makePair(RT, "RT"), + makePair(LT_CLICK, "LTClick"), + makePair(RT_CLICK, "RTClick"), // Finger abstractions makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), diff --git a/libraries/controllers/src/controllers/StandardController.h b/libraries/controllers/src/controllers/StandardController.h index fee608f822..58c3ecaa30 100644 --- a/libraries/controllers/src/controllers/StandardController.h +++ b/libraries/controllers/src/controllers/StandardController.h @@ -28,11 +28,9 @@ public: virtual EndpointPointer createEndpoint(const Input& input) const override; virtual Input::NamedVector getAvailableInputs() const override; virtual QStringList getDefaultMappingConfigs() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; virtual void focusOutEvent() override; StandardController(); - virtual ~StandardController(); }; } diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index f101ba6c51..c21d8a2f6e 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -43,6 +43,10 @@ namespace controller { LEFT_SECONDARY_THUMB_TOUCH, LS_TOUCH, LEFT_THUMB_UP, + LS_CENTER, + LS_X, + LS_Y, + LT_CLICK, RIGHT_PRIMARY_THUMB, RIGHT_SECONDARY_THUMB, @@ -50,6 +54,10 @@ namespace controller { RIGHT_SECONDARY_THUMB_TOUCH, RS_TOUCH, RIGHT_THUMB_UP, + RS_CENTER, + RS_X, + RS_Y, + RT_CLICK, LEFT_PRIMARY_INDEX, LEFT_SECONDARY_INDEX, @@ -62,9 +70,7 @@ namespace controller { RIGHT_SECONDARY_INDEX_TOUCH, RIGHT_INDEX_POINT, - LEFT_GRIP, LEFT_GRIP_TOUCH, - RIGHT_GRIP, RIGHT_GRIP_TOUCH, NUM_STANDARD_BUTTONS @@ -81,9 +87,9 @@ namespace controller { // Triggers LT, RT, - // Grips (Oculus touch squeeze) - LG, - RG, + // Grips + LEFT_GRIP, + RIGHT_GRIP, NUM_STANDARD_AXES, LZ = LT, RZ = RT diff --git a/libraries/controllers/src/controllers/StateController.h b/libraries/controllers/src/controllers/StateController.h index 57414c3ae8..c18c9df27c 100644 --- a/libraries/controllers/src/controllers/StateController.h +++ b/libraries/controllers/src/controllers/StateController.h @@ -35,10 +35,7 @@ public: const QString& getName() const { return _name; } // Device functions - virtual Input::NamedVector getAvailableInputs() const override; - - void update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) override {} - void focusOutEvent() override {} + Input::NamedVector getAvailableInputs() const override; void setInputVariant(const QString& name, ReadLambda lambda); diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index c0d3ff40c0..7490a44c11 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -62,14 +62,6 @@ namespace controller { UserInputMapper::~UserInputMapper() { } -int UserInputMapper::recordDeviceOfType(const QString& deviceName) { - if (!_deviceCounts.contains(deviceName)) { - _deviceCounts[deviceName] = 0; - } - _deviceCounts[deviceName] += 1; - return _deviceCounts[deviceName]; -} - void UserInputMapper::registerDevice(InputDevice::Pointer device) { Locker locker(_lock); if (device->_deviceID == Input::INVALID_DEVICE) { @@ -77,8 +69,6 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) { } const auto& deviceID = device->_deviceID; - recordDeviceOfType(device->getName()); - qCDebug(controllers) << "Registered input device <" << device->getName() << "> deviceID = " << deviceID; for (const auto& inputMapping : device->getAvailableInputs()) { @@ -134,6 +124,15 @@ void UserInputMapper::removeDevice(int deviceID) { _mappingsByDevice.erase(mappingsEntry); } + for (const auto& inputMapping : device->getAvailableInputs()) { + const auto& input = inputMapping.first; + auto endpoint = _endpointsByInput.find(input); + if (endpoint != _endpointsByInput.end()) { + _inputsByEndpoint.erase((*endpoint).second); + _endpointsByInput.erase(input); + } + } + _registeredDevices.erase(proxyEntry); emit hardwareChanged(); @@ -336,10 +335,28 @@ QVector UserInputMapper::getActionNames() const { return result; } -static int actionMetaTypeId = qRegisterMetaType(); -static int inputMetaTypeId = qRegisterMetaType(); -static int inputPairMetaTypeId = qRegisterMetaType(); -static int poseMetaTypeId = qRegisterMetaType("Pose"); +bool UserInputMapper::triggerHapticPulse(float strength, float duration, controller::Hand hand) { + Locker locker(_lock); + bool toReturn = false; + for (auto device : _registeredDevices) { + toReturn = toReturn || device.second->triggerHapticPulse(strength, duration, hand); + } + return toReturn; +} + +bool UserInputMapper::triggerHapticPulseOnDevice(uint16 deviceID, float strength, float duration, controller::Hand hand) { + Locker locker(_lock); + if (_registeredDevices.find(deviceID) != _registeredDevices.end()) { + return _registeredDevices[deviceID]->triggerHapticPulse(strength, duration, hand); + } + return false; +} + +int actionMetaTypeId = qRegisterMetaType(); +int inputMetaTypeId = qRegisterMetaType(); +int inputPairMetaTypeId = qRegisterMetaType(); +int poseMetaTypeId = qRegisterMetaType("Pose"); +int handMetaTypeId = qRegisterMetaType(); QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input); void inputFromScriptValue(const QScriptValue& object, Input& input); @@ -347,6 +364,8 @@ QScriptValue actionToScriptValue(QScriptEngine* engine, const Action& action); void actionFromScriptValue(const QScriptValue& object, Action& action); QScriptValue inputPairToScriptValue(QScriptEngine* engine, const Input::NamedPair& inputPair); void inputPairFromScriptValue(const QScriptValue& object, Input::NamedPair& inputPair); +QScriptValue handToScriptValue(QScriptEngine* engine, const controller::Hand& hand); +void handFromScriptValue(const QScriptValue& object, controller::Hand& hand); QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input) { QScriptValue obj = engine->newObject(); @@ -385,12 +404,21 @@ void inputPairFromScriptValue(const QScriptValue& object, Input::NamedPair& inpu inputPair.second = QString(object.property("inputName").toVariant().toString()); } +QScriptValue handToScriptValue(QScriptEngine* engine, const controller::Hand& hand) { + return engine->newVariant((int)hand); +} + +void handFromScriptValue(const QScriptValue& object, controller::Hand& hand) { + hand = Hand(object.toVariant().toInt()); +} + void UserInputMapper::registerControllerTypes(QScriptEngine* engine) { qScriptRegisterSequenceMetaType >(engine); qScriptRegisterSequenceMetaType(engine); qScriptRegisterMetaType(engine, actionToScriptValue, actionFromScriptValue); qScriptRegisterMetaType(engine, inputToScriptValue, inputFromScriptValue); qScriptRegisterMetaType(engine, inputPairToScriptValue, inputPairFromScriptValue); + qScriptRegisterMetaType(engine, handToScriptValue, handFromScriptValue); qScriptRegisterMetaType(engine, Pose::toScriptValue, Pose::fromScriptValue); } diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 9c79415b6e..874e5054ea 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -89,6 +89,8 @@ namespace controller { void setActionState(Action action, float value) { _actionStates[toInt(action)] = value; } void deltaActionState(Action action, float delta) { _actionStates[toInt(action)] += delta; } void setActionState(Action action, const Pose& value) { _poseStates[toInt(action)] = value; } + bool triggerHapticPulse(float strength, float duration, controller::Hand hand); + bool triggerHapticPulseOnDevice(uint16 deviceID, float strength, float duration, controller::Hand hand); static Input makeStandardInput(controller::StandardButtonChannel button); static Input makeStandardInput(controller::StandardAxisChannel axis); @@ -141,9 +143,6 @@ namespace controller { std::vector _poseStates = std::vector(toInt(Action::NUM_ACTIONS)); std::vector _lastStandardStates = std::vector(); - int recordDeviceOfType(const QString& deviceName); - QHash _deviceCounts; - static float getValue(const EndpointPointer& endpoint, bool peek = false); static Pose getPose(const EndpointPointer& endpoint, bool peek = false); @@ -199,6 +198,7 @@ Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(controller::Input) Q_DECLARE_METATYPE(controller::Action) Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(controller::Hand) // Cheating. using UserInputMapper = controller::UserInputMapper; diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index 5ae52893e0..7dedfda3cb 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -26,6 +26,7 @@ #include "filters/InvertFilter.h" #include "filters/PulseFilter.h" #include "filters/ScaleFilter.h" +#include "conditionals/AndConditional.h" using namespace controller; @@ -58,12 +59,22 @@ QObject* RouteBuilderProxy::peek(bool enable) { } QObject* RouteBuilderProxy::when(const QScriptValue& expression) { - _route->conditional = _parent.conditionalFor(expression); + auto newConditional = _parent.conditionalFor(expression); + if (_route->conditional) { + _route->conditional = ConditionalPointer(new AndConditional(_route->conditional, newConditional)); + } else { + _route->conditional = newConditional; + } return this; } QObject* RouteBuilderProxy::whenQml(const QJSValue& expression) { - _route->conditional = _parent.conditionalFor(expression); + auto newConditional = _parent.conditionalFor(expression); + if (_route->conditional) { + _route->conditional = ConditionalPointer(new AndConditional(_route->conditional, newConditional)); + } else { + _route->conditional = newConditional; + } return this; } diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt index f2d58d825e..fe08647074 100644 --- a/libraries/display-plugins/CMakeLists.txt +++ b/libraries/display-plugins/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME display-plugins) setup_hifi_library(OpenGL) -link_hifi_libraries(shared plugins gl gpu-gl ui) +link_hifi_libraries(shared plugins ui-plugins gl gpu-gl ui) target_opengl() diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.cpp b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.cpp index 4b8d957e5f..d068bef3b0 100644 --- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.cpp +++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.cpp @@ -11,7 +11,7 @@ #include #include "DisplayPlugin.h" -#include +#include static Setting::Handle IPD_SCALE_HANDLE("hmd.ipdScale", 1.0f); diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h index 5df58ce677..f260fa959f 100644 --- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h +++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h @@ -31,6 +31,7 @@ public: signals: void IPDScaleChanged(); + void displayModeChanged(bool isHMDMode); private: float _IPDScale{ 1.0 }; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 48dda1f73d..f488a805c6 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include const QString Basic2DWindowOpenGLDisplayPlugin::NAME("Desktop"); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index f9d527de8f..56be8e1cf9 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -336,7 +336,9 @@ void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3& } glm::mat4 CompositorHelper::getUiTransform() const { - return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()); + glm::mat4 modelMat; + _modelTransform.getMatrix(modelMat); + return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()) * modelMat; } //Finds the collision point of a world space ray @@ -346,7 +348,7 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c auto relativePosition = vec3(relativePosition4) / relativePosition4.w; auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction; - float uiRadius = _oculusUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale + float uiRadius = _hmdUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale float instersectionDistance; if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){ @@ -407,84 +409,25 @@ void CompositorHelper::updateTooltips() { //} } -static const float FADE_DURATION = 500.0f; -static const float FADE_IN_ALPHA = 1.0f; -static const float FADE_OUT_ALPHA = 0.0f; - -void CompositorHelper::startFadeFailsafe(float endValue) { - _fadeStarted = usecTimestampNow(); - _fadeFailsafeEndValue = endValue; - - const int SLIGHT_DELAY = 10; - QTimer::singleShot(FADE_DURATION + SLIGHT_DELAY, [this]{ - checkFadeFailsafe(); - }); -} - -void CompositorHelper::checkFadeFailsafe() { - auto elapsedInFade = usecTimestampNow() - _fadeStarted; - if (elapsedInFade > FADE_DURATION) { - setAlpha(_fadeFailsafeEndValue); - } -} - -void CompositorHelper::fadeIn() { - _fadeInAlpha = true; - - _alphaPropertyAnimation->setDuration(FADE_DURATION); - _alphaPropertyAnimation->setStartValue(_alpha); - _alphaPropertyAnimation->setEndValue(FADE_IN_ALPHA); - _alphaPropertyAnimation->start(); - - // Sometimes, this "QPropertyAnimation" fails to complete the animation, and we end up with a partially faded - // state. So we will also have this fail-safe, where we record the timestamp of the fadeRequest, and the target - // value of the fade, and if after that time we still haven't faded all the way, we will kick it to the final - // fade value - startFadeFailsafe(FADE_IN_ALPHA); -} - -void CompositorHelper::fadeOut() { - _fadeInAlpha = false; - - _alphaPropertyAnimation->setDuration(FADE_DURATION); - _alphaPropertyAnimation->setStartValue(_alpha); - _alphaPropertyAnimation->setEndValue(FADE_OUT_ALPHA); - _alphaPropertyAnimation->start(); - startFadeFailsafe(FADE_OUT_ALPHA); -} - -void CompositorHelper::toggle() { - if (_fadeInAlpha) { - fadeOut(); - } else { - fadeIn(); - } -} - +// eyePose and headPosition are in sensor space. +// the resulting matrix should be in view space. glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::vec3& headPosition) const { glm::mat4 result; if (isHMD()) { - vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize); - auto reticlePosition = getReticlePosition(); - auto spherical = overlayToSpherical(reticlePosition); - // The pointer transform relative to the sensor - auto pointerTransform = glm::mat4_cast(quat(vec3(-spherical.y, spherical.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); - float reticleDepth = getReticleDepth(); - if (reticleDepth != 1.0f) { - // Cursor position in UI space - auto cursorPosition = vec3(pointerTransform[3]) / pointerTransform[3].w; - // Ray to the cursor, in UI space - auto cursorRay = glm::normalize(cursorPosition - headPosition) * reticleDepth; - // Move the ray to be relative to the head pose - pointerTransform[3] = vec4(cursorRay + headPosition, 1); - // Scale up the cursor because of distance - reticleScale *= reticleDepth; + vec2 spherical = overlayToSpherical(getReticlePosition()); + vec3 overlaySurfacePoint = getPoint(spherical.x, spherical.y); // overlay space + vec3 sensorSurfacePoint = _modelTransform.transform(overlaySurfacePoint); // sensor space + vec3 d = sensorSurfacePoint - headPosition; + vec3 reticlePosition; + if (glm::length(d) >= EPSILON) { + d = glm::normalize(d); + } else { + d = glm::normalize(overlaySurfacePoint); } - glm::mat4 overlayXfm; - _modelTransform.getMatrix(overlayXfm); - pointerTransform = overlayXfm * pointerTransform; - pointerTransform = glm::inverse(eyePose) * pointerTransform; - result = glm::scale(pointerTransform, reticleScale); + reticlePosition = headPosition + (d * getReticleDepth()); + quat reticleOrientation = cancelOutRoll(glm::quat_cast(_currentDisplayPlugin->getHeadPose())); + vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth()); + return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition); } else { static const float CURSOR_PIXEL_SIZE = 32.0f; const auto canvasSize = vec2(toGlm(_renderingWidget->size()));; @@ -499,3 +442,12 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const } return result; } + + +QVariant ReticleInterface::getPosition() const { + return vec2toVariant(_compositor->getReticlePosition()); +} + +void ReticleInterface::setPosition(QVariant position) { + _compositor->setReticlePosition(vec2FromVariant(position)); +} diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index c0b53b329e..b0b96d86be 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -38,7 +38,7 @@ const float MAGNIFY_MULT = 2.0f; class CompositorHelper : public QObject, public Dependency { Q_OBJECT - Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha) + Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha NOTIFY alphaChanged) Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop) public: static const uvec2 VIRTUAL_SCREEN_SIZE; @@ -75,12 +75,8 @@ public: void setModelTransform(const Transform& transform) { _modelTransform = transform; } const Transform& getModelTransform() const { return _modelTransform; } - void fadeIn(); - void fadeOut(); - void toggle(); - float getAlpha() const { return _alpha; } - void setAlpha(float alpha) { _alpha = alpha; } + void setAlpha(float alpha) { if (alpha != _alpha) { emit alphaChanged(); _alpha = alpha; } } bool getReticleVisible() const { return _reticleVisible; } void setReticleVisible(bool visible) { _reticleVisible = visible; } @@ -113,10 +109,11 @@ public: void setReticleOverDesktop(bool value) { _isOverDesktop = value; } void setDisplayPlugin(const DisplayPluginPointer& displayPlugin) { _currentDisplayPlugin = displayPlugin; } - void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; _currentFrame = frame; } + void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; } signals: void allowMouseCaptureChanged(); + void alphaChanged(); protected slots: void sendFakeMouseEvent(); @@ -127,7 +124,6 @@ private: DisplayPluginPointer _currentDisplayPlugin; glm::mat4 _currentCamera; - uint32_t _currentFrame { 0 }; QWidget* _renderingWidget{ nullptr }; //// Support for hovering and tooltips @@ -144,16 +140,7 @@ private: float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO }; float _alpha { 1.0f }; - float _prevAlpha { 1.0f }; - float _fadeInAlpha { true }; - float _oculusUIRadius { 1.0f }; - - quint64 _fadeStarted { 0 }; - float _fadeFailsafeEndValue { 1.0f }; - void checkFadeFailsafe(); - void startFadeFailsafe(float endValue); - - int _reticleQuad; + float _hmdUIRadius { 1.0f }; int _previousBorderWidth { -1 }; int _previousBorderHeight { -1 }; @@ -187,7 +174,7 @@ private: // Scripting interface available to control the Reticle class ReticleInterface : public QObject { Q_OBJECT - Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) + Q_PROPERTY(QVariant position READ getPosition WRITE setPosition) Q_PROPERTY(bool visible READ getVisible WRITE setVisible) Q_PROPERTY(float depth READ getDepth WRITE setDepth) Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition) @@ -211,8 +198,8 @@ public: Q_INVOKABLE float getDepth() { return _compositor->getReticleDepth(); } Q_INVOKABLE void setDepth(float depth) { _compositor->setReticleDepth(depth); } - Q_INVOKABLE glm::vec2 getPosition() { return _compositor->getReticlePosition(); } - Q_INVOKABLE void setPosition(glm::vec2 position) { _compositor->setReticlePosition(position); } + Q_INVOKABLE QVariant getPosition() const; + Q_INVOKABLE void setPosition(QVariant position); Q_INVOKABLE glm::vec2 getMaximumPosition() { return _compositor->getReticleMaximumPosition(); } diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index 3f642072a0..4fadbdb94b 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -10,7 +10,7 @@ #include "NullDisplayPlugin.h" #include -#include +#include const QString NullDisplayPlugin::NAME("NullDisplayPlugin"); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index d34b698410..e0c87fbbed 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -16,18 +16,22 @@ #include #include +#if defined(Q_OS_MAC) +#include +#endif #include #include #include #include #include #include -#include +#include #include #include #include #include #include "CompositorHelper.h" +#include #if THREADED_PRESENT @@ -202,6 +206,7 @@ private: #endif + OpenGLDisplayPlugin::OpenGLDisplayPlugin() { _sceneTextureEscrow.setRecycler([this](const gpu::TexturePointer& texture){ cleanupForSceneTexture(texture); @@ -213,9 +218,10 @@ OpenGLDisplayPlugin::OpenGLDisplayPlugin() { } void OpenGLDisplayPlugin::cleanupForSceneTexture(const gpu::TexturePointer& sceneTexture) { - Lock lock(_mutex); - Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture)); - _sceneTextureToFrameIndexMap.remove(sceneTexture); + withRenderThreadLock([&] { + Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture)); + _sceneTextureToFrameIndexMap.remove(sceneTexture); + }); } @@ -233,10 +239,11 @@ bool OpenGLDisplayPlugin::activate() { cursorData.hotSpot = vec2(0.5f); } } - + if (!_container) { + return false; + } _vsyncSupported = _container->getPrimaryWidget()->isVsyncSupported(); - #if THREADED_PRESENT // Start the present thread if necessary QSharedPointer presentThread; @@ -272,11 +279,27 @@ bool OpenGLDisplayPlugin::activate() { _container->makeRenderingContextCurrent(); #endif - return DisplayPlugin::activate(); + auto compositorHelper = DependencyManager::get(); + connect(compositorHelper.data(), &CompositorHelper::alphaChanged, [this] { + auto compositorHelper = DependencyManager::get(); + auto animation = new QPropertyAnimation(this, "overlayAlpha"); + animation->setDuration(200); + animation->setEndValue(compositorHelper->getAlpha()); + animation->start(); + }); + + if (isHmd() && (getHmdScreen() >= 0)) { + _container->showDisplayPluginsTools(); + } + + return Parent::activate(); } void OpenGLDisplayPlugin::deactivate() { + auto compositorHelper = DependencyManager::get(); + disconnect(compositorHelper.data()); + #if THREADED_PRESENT auto presentThread = DependencyManager::get(); // Does not return until the GL transition has completeed @@ -288,7 +311,16 @@ void OpenGLDisplayPlugin::deactivate() { _container->makeRenderingContextCurrent(); #endif internalDeactivate(); - DisplayPlugin::deactivate(); + + _container->showDisplayPluginsTools(false); + if (!_container->currentDisplayActions().isEmpty()) { + auto menu = _container->getPrimaryMenu(); + foreach(auto itemInfo, _container->currentDisplayActions()) { + menu->removeMenuItem(itemInfo.first, itemInfo.second); + } + _container->currentDisplayActions().clear(); + } + Parent::deactivate(); } @@ -394,10 +426,9 @@ void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::Tex return; } - { - Lock lock(_mutex); + withRenderThreadLock([&] { _sceneTextureToFrameIndexMap[sceneTexture] = frameIndex; - } + }); // Submit it to the presentation thread via escrow _sceneTextureEscrow.submit(sceneTexture); @@ -431,11 +462,12 @@ void OpenGLDisplayPlugin::updateTextures() { } void OpenGLDisplayPlugin::updateFrameData() { - Lock lock(_mutex); - auto previousFrameIndex = _currentPresentFrameIndex; - _currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture]; - auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1; - _droppedFrameRate.increment(skippedCount); + withPresentThreadLock([&] { + auto previousFrameIndex = _currentPresentFrameIndex; + _currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture]; + auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1; + _droppedFrameRate.increment(skippedCount); + }); } void OpenGLDisplayPlugin::compositeOverlay() { @@ -444,25 +476,22 @@ void OpenGLDisplayPlugin::compositeOverlay() { auto compositorHelper = DependencyManager::get(); useProgram(_program); + // set the alpha + Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); // check the alpha - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); - - // Overlay draw - if (isStereo()) { - Uniform(*_program, _mvpUniform).Set(mat4()); - for_each_eye([&](Eye eye) { - eyeViewport(eye); - drawUnitQuad(); - }); - } else { - // Overlay draw - Uniform(*_program, _mvpUniform).Set(mat4()); + // Overlay draw + if (isStereo()) { + Uniform(*_program, _mvpUniform).Set(mat4()); + for_each_eye([&](Eye eye) { + eyeViewport(eye); drawUnitQuad(); - } + }); + } else { + // Overlay draw + Uniform(*_program, _mvpUniform).Set(mat4()); + drawUnitQuad(); } + // restore the alpha Uniform(*_program, _alphaUniform).Set(1.0); } @@ -471,23 +500,19 @@ void OpenGLDisplayPlugin::compositePointer() { auto compositorHelper = DependencyManager::get(); useProgram(_program); - // check the alpha - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); - - Uniform(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4())); - if (isStereo()) { - for_each_eye([&](Eye eye) { - eyeViewport(eye); - drawUnitQuad(); - }); - } else { + // set the alpha + Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); + Uniform(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4())); + if (isStereo()) { + for_each_eye([&](Eye eye) { + eyeViewport(eye); drawUnitQuad(); - } + }); + } else { + drawUnitQuad(); } Uniform(*_program, _mvpUniform).Set(mat4()); + // restore the alpha Uniform(*_program, _alphaUniform).Set(1.0); } @@ -507,14 +532,14 @@ void OpenGLDisplayPlugin::compositeLayers() { } _compositeFramebuffer->Bound(Framebuffer::Target::Draw, [&] { Context::Viewport(targetRenderSize.x, targetRenderSize.y); - Context::Clear().DepthBuffer(); - glBindTexture(GL_TEXTURE_2D, getSceneTextureId()); - compositeScene(); + auto sceneTextureId = getSceneTextureId(); auto overlayTextureId = getOverlayTextureId(); + glBindTexture(GL_TEXTURE_2D, sceneTextureId); + compositeScene(); if (overlayTextureId) { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, overlayTextureId); + Context::Enable(Capability::Blend); + Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha); compositeOverlay(); auto compositorHelper = DependencyManager::get(); @@ -522,11 +547,16 @@ void OpenGLDisplayPlugin::compositeLayers() { auto& cursorManager = Cursor::Manager::instance(); const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()]; glBindTexture(GL_TEXTURE_2D, cursorData.texture); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, overlayTextureId); compositePointer(); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); } glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_BLEND); + Context::Disable(Capability::Blend); } + compositeExtra(); }); } @@ -564,7 +594,11 @@ float OpenGLDisplayPlugin::newFramePresentRate() const { } float OpenGLDisplayPlugin::droppedFrameRate() const { - return _droppedFrameRate.rate(); + float result; + withRenderThreadLock([&] { + result = _droppedFrameRate.rate(); + }); + return result; } float OpenGLDisplayPlugin::presentRate() const { @@ -581,8 +615,14 @@ void OpenGLDisplayPlugin::enableVsync(bool enable) { if (!_vsyncSupported) { return; } -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) wglSwapIntervalEXT(enable ? 1 : 0); +#elif defined(Q_OS_MAC) + GLint interval = enable ? 1 : 0; + CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); +#else + // TODO: Fill in for linux + return; #endif } @@ -590,9 +630,14 @@ bool OpenGLDisplayPlugin::isVsyncEnabled() { if (!_vsyncSupported) { return true; } -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) return wglGetSwapIntervalEXT() != 0; +#elif defined(Q_OS_MAC) + GLint interval; + CGLGetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); + return interval != 0; #else + // TODO: Fill in for linux return true; #endif } @@ -616,12 +661,13 @@ void OpenGLDisplayPlugin::withMainThreadContext(std::function f) const { } QImage OpenGLDisplayPlugin::getScreenshot() const { - QImage result; + using namespace oglplus; + QImage screenshot(_compositeFramebuffer->size.x, _compositeFramebuffer->size.y, QImage::Format_RGBA8888); withMainThreadContext([&] { - static auto widget = _container->getPrimaryWidget(); - result = widget->grabFrameBuffer(); + Framebuffer::Bind(Framebuffer::Target::Read, _compositeFramebuffer->fbo); + Context::ReadPixels(0, 0, _compositeFramebuffer->size.x, _compositeFramebuffer->size.y, enums::PixelDataFormat::RGBA, enums::PixelDataType::UnsignedByte, screenshot.bits()); }); - return result; + return screenshot.mirrored(false, true); } uint32_t OpenGLDisplayPlugin::getSceneTextureId() const { @@ -679,3 +725,18 @@ void OpenGLDisplayPlugin::useProgram(const ProgramPtr& program) { _activeProgram = program; } } + +void OpenGLDisplayPlugin::assertIsRenderThread() const { + Q_ASSERT(QThread::currentThread() != _presentThread); +} + +void OpenGLDisplayPlugin::assertIsPresentThread() const { + Q_ASSERT(QThread::currentThread() == _presentThread); +} + +bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + withRenderThreadLock([&] { + _compositeOverlayAlpha = _overlayAlpha; + }); + return Parent::beginFrameRender(frameIndex); +} diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index c87ff1bc93..068b236289 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -24,6 +24,9 @@ #define THREADED_PRESENT 1 class OpenGLDisplayPlugin : public DisplayPlugin { + Q_OBJECT + Q_PROPERTY(float overlayAlpha MEMBER _overlayAlpha) + using Parent = DisplayPlugin; protected: using Mutex = std::mutex; using Lock = std::unique_lock; @@ -60,6 +63,7 @@ public: float droppedFrameRate() const override; + bool beginFrameRender(uint32_t frameIndex) override; protected: #if THREADED_PRESENT friend class PresentThread; @@ -74,6 +78,7 @@ protected: virtual void compositeScene(); virtual void compositeOverlay(); virtual void compositePointer(); + virtual void compositeExtra() {}; virtual bool hasFocus() const override; @@ -109,12 +114,12 @@ protected: int32_t _alphaUniform { -1 }; ShapeWrapperPtr _plane; - mutable Mutex _mutex; RateCounter<> _droppedFrameRate; RateCounter<> _newFrameRate; RateCounter<> _presentRate; QMap _sceneTextureToFrameIndexMap; uint32_t _currentPresentFrameIndex { 0 }; + float _compositeOverlayAlpha{ 1.0f }; gpu::TexturePointer _currentSceneTexture; gpu::TexturePointer _currentOverlayTexture; @@ -135,8 +140,28 @@ protected: BasicFramebufferWrapperPtr _compositeFramebuffer; bool _lockCurrentTexture { false }; + void assertIsRenderThread() const; + void assertIsPresentThread() const; + + template + void withPresentThreadLock(F f) const { + assertIsPresentThread(); + Lock lock(_presentMutex); + f(); + } + + template + void withRenderThreadLock(F f) const { + assertIsRenderThread(); + Lock lock(_presentMutex); + f(); + } + private: + // Any resource shared by the main thread and the presentation thread must + // be serialized through this mutex + mutable Mutex _presentMutex; ProgramPtr _activeProgram; + float _overlayAlpha{ 1.0f }; }; - diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 4e594d89ed..1bfa6c7921 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -9,17 +9,25 @@ #include #include +#include #include +#include +#include #include #include #include -#include +#include #include #include #include +#include +#include + +#include + #include "../Logging.h" #include "../CompositorHelper.h" @@ -28,6 +36,9 @@ static const QString REPROJECTION = "Allow Reprojection"; static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; static const QString DEVELOPER_MENU_PATH = "Developer>" + DisplayPlugin::MENU_PATH(); static const bool DEFAULT_MONO_VIEW = true; +static const int NUMBER_OF_HANDS = 2; +static const glm::mat4 IDENTITY_MATRIX; + glm::uvec2 HmdDisplayPlugin::getRecommendedUiSize() const { return CompositorHelper::VIRTUAL_SCREEN_SIZE; @@ -37,7 +48,6 @@ QRect HmdDisplayPlugin::getRecommendedOverlayRect() const { return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT; } - bool HmdDisplayPlugin::internalActivate() { _monoPreview = _container->getBoolSetting("monoPreview", DEFAULT_MONO_VIEW); @@ -58,163 +68,189 @@ bool HmdDisplayPlugin::internalActivate() { _eyeInverseProjections[eye] = glm::inverse(_eyeProjections[eye]); }); + if (_previewTextureID == 0) { + QImage previewTexture(PathUtils::resourcesPath() + "images/preview.png"); + if (!previewTexture.isNull()) { + glGenTextures(1, &_previewTextureID); + glBindTexture(GL_TEXTURE_2D, _previewTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, previewTexture.width(), previewTexture.height(), 0, + GL_BGRA, GL_UNSIGNED_BYTE, previewTexture.mirrored(false, true).bits()); + using namespace oglplus; + Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); + Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); + glBindTexture(GL_TEXTURE_2D, 0); + _previewAspect = ((float)previewTexture.width())/((float)previewTexture.height()); + _firstPreview = true; + } + } + return Parent::internalActivate(); } - -static const char * REPROJECTION_VS = R"VS(#version 410 core -in vec3 Position; -in vec2 TexCoord; - -out vec3 vPosition; -out vec2 vTexCoord; - -void main() { - gl_Position = vec4(Position, 1); - vTexCoord = TexCoord; - vPosition = Position; +void HmdDisplayPlugin::internalDeactivate() { + if (_previewTextureID != 0) { + glDeleteTextures(1, &_previewTextureID); + _previewTextureID = 0; + } + Parent::internalDeactivate(); } -)VS"; - -static GLint REPROJECTION_MATRIX_LOCATION = -1; -static GLint INVERSE_PROJECTION_MATRIX_LOCATION = -1; -static GLint PROJECTION_MATRIX_LOCATION = -1; -static const char * REPROJECTION_FS = R"FS(#version 410 core - -uniform sampler2D sampler; -uniform mat3 reprojection = mat3(1); -uniform mat4 inverseProjections[2]; -uniform mat4 projections[2]; - -in vec2 vTexCoord; -in vec3 vPosition; - -out vec4 FragColor; - -void main() { - vec2 uv = vTexCoord; - - mat4 eyeInverseProjection; - mat4 eyeProjection; - - float xoffset = 1.0; - vec2 uvmin = vec2(0.0); - vec2 uvmax = vec2(1.0); - // determine the correct projection and inverse projection to use. - if (vTexCoord.x < 0.5) { - uvmax.x = 0.5; - eyeInverseProjection = inverseProjections[0]; - eyeProjection = projections[0]; - } else { - xoffset = -1.0; - uvmin.x = 0.5; - uvmax.x = 1.0; - eyeInverseProjection = inverseProjections[1]; - eyeProjection = projections[1]; - } - - // Account for stereo in calculating the per-eye NDC coordinates - vec4 ndcSpace = vec4(vPosition, 1.0); - ndcSpace.x *= 2.0; - ndcSpace.x += xoffset; - - // Convert from NDC to eyespace - vec4 eyeSpace = eyeInverseProjection * ndcSpace; - eyeSpace /= eyeSpace.w; - - // Convert to a noramlized ray - vec3 ray = eyeSpace.xyz; - ray = normalize(ray); - - // Adjust the ray by the rotation - ray = reprojection * ray; - - // Project back on to the texture plane - ray *= eyeSpace.z / ray.z; - - // Update the eyespace vector - eyeSpace.xyz = ray; - - // Reproject back into NDC - ndcSpace = eyeProjection * eyeSpace; - ndcSpace /= ndcSpace.w; - ndcSpace.x -= xoffset; - ndcSpace.x /= 2.0; - - // Calculate the new UV coordinates - uv = (ndcSpace.xy / 2.0) + 0.5; - if (any(greaterThan(uv, uvmax)) || any(lessThan(uv, uvmin))) { - FragColor = vec4(0.0, 0.0, 0.0, 1.0); - } else { - FragColor = texture(sampler, uv); - } -} -)FS"; - -#ifdef DEBUG_REPROJECTION_SHADER -#include -#include -#include -#include - -static const QString REPROJECTION_FS_FILE = "c:/Users/bdavis/Git/hifi/interface/resources/shaders/reproject.frag"; - -static ProgramPtr getReprojectionProgram() { - static ProgramPtr _currentProgram; - uint64_t now = usecTimestampNow(); - static uint64_t _lastFileCheck = now; - - bool modified = false; - if ((now - _lastFileCheck) > USECS_PER_MSEC * 100) { - QFileInfo info(REPROJECTION_FS_FILE); - QDateTime lastModified = info.lastModified(); - static QDateTime _lastModified = lastModified; - qDebug() << lastModified.toTime_t(); - qDebug() << _lastModified.toTime_t(); - if (lastModified > _lastModified) { - _lastModified = lastModified; - modified = true; - } - } - - if (!_currentProgram || modified) { - _currentProgram.reset(); - try { - QFile shaderFile(REPROJECTION_FS_FILE); - shaderFile.open(QIODevice::ReadOnly); - QString fragment = shaderFile.readAll(); - compileProgram(_currentProgram, REPROJECTION_VS, fragment.toLocal8Bit().data()); - } catch (const std::runtime_error& error) { - qDebug() << "Failed to build: " << error.what(); - } - if (!_currentProgram) { - _currentProgram = loadDefaultShader(); - } - } - return _currentProgram; -} -#endif - - void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); // Only enable mirroring if we know vsync is disabled + // On Mac, this won't work due to how the contexts are handled, so don't try +#if !defined(Q_OS_MAC) enableVsync(false); +#endif _enablePreview = !isVsyncEnabled(); _sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO); - compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS); - using namespace oglplus; - REPROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "reprojection").Location(); - INVERSE_PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "inverseProjections").Location(); - PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "projections").Location(); + if (!_enablePreview) { + const std::string version("#version 410 core\n"); + compileProgram(_previewProgram, version + DrawUnitQuadTexcoord_vert, version + DrawTexture_frag); + _previewUniforms.previewTexture = Uniform(*_previewProgram, "colorMap").Location(); + } + + updateReprojectionProgram(); + updateOverlayProgram(); + updateLaserProgram(); + + _laserGeometry = loadLaser(_laserProgram); +} +//#define LIVE_SHADER_RELOAD 1 + +static QString readFile(const QString& filename) { + QFile file(filename); + file.open(QFile::Text | QFile::ReadOnly); + QString result; + result.append(QTextStream(&file).readAll()); + return result; +} + +void HmdDisplayPlugin::updateReprojectionProgram() { + static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_reproject.vert"; + static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_reproject.frag"; +#if LIVE_SHADER_RELOAD + static qint64 vsBuiltAge = 0; + static qint64 fsBuiltAge = 0; + QFileInfo vsInfo(vsFile); + QFileInfo fsInfo(fsFile); + auto vsAge = vsInfo.lastModified().toMSecsSinceEpoch(); + auto fsAge = fsInfo.lastModified().toMSecsSinceEpoch(); + if (!_reprojectionProgram || vsAge > vsBuiltAge || fsAge > fsBuiltAge) { + vsBuiltAge = vsAge; + fsBuiltAge = fsAge; +#else + if (!_reprojectionProgram) { +#endif + QString vsSource = readFile(vsFile); + QString fsSource = readFile(fsFile); + ProgramPtr program; + try { + compileProgram(program, vsSource.toLocal8Bit().toStdString(), fsSource.toLocal8Bit().toStdString()); + if (program) { + using namespace oglplus; + _reprojectionUniforms.reprojectionMatrix = Uniform(*program, "reprojection").Location(); + _reprojectionUniforms.inverseProjectionMatrix = Uniform(*program, "inverseProjections").Location(); + _reprojectionUniforms.projectionMatrix = Uniform(*program, "projections").Location(); + _reprojectionProgram = program; + } + } catch (std::runtime_error& error) { + qWarning() << "Error building reprojection shader " << error.what(); + } + } + +} + +void HmdDisplayPlugin::updateLaserProgram() { + static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_hand_lasers.vert"; + static const QString gsFile = PathUtils::resourcesPath() + "/shaders/hmd_hand_lasers.geom"; + static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_hand_lasers.frag"; + +#if LIVE_SHADER_RELOAD + static qint64 vsBuiltAge = 0; + static qint64 gsBuiltAge = 0; + static qint64 fsBuiltAge = 0; + QFileInfo vsInfo(vsFile); + QFileInfo fsInfo(fsFile); + QFileInfo gsInfo(fsFile); + auto vsAge = vsInfo.lastModified().toMSecsSinceEpoch(); + auto fsAge = fsInfo.lastModified().toMSecsSinceEpoch(); + auto gsAge = gsInfo.lastModified().toMSecsSinceEpoch(); + if (!_laserProgram || vsAge > vsBuiltAge || fsAge > fsBuiltAge || gsAge > gsBuiltAge) { + vsBuiltAge = vsAge; + gsBuiltAge = gsAge; + fsBuiltAge = fsAge; +#else + if (!_laserProgram) { +#endif + + QString vsSource = readFile(vsFile); + QString fsSource = readFile(fsFile); + QString gsSource = readFile(gsFile); + ProgramPtr program; + try { + compileProgram(program, vsSource.toLocal8Bit().toStdString(), gsSource.toLocal8Bit().toStdString(), fsSource.toLocal8Bit().toStdString()); + if (program) { + using namespace oglplus; + _laserUniforms.color = Uniform(*program, "color").Location(); + _laserUniforms.mvp = Uniform(*program, "mvp").Location(); + _laserProgram = program; + } + } catch (std::runtime_error& error) { + qWarning() << "Error building hand laser composite shader " << error.what(); + } + } +} + +void HmdDisplayPlugin::updateOverlayProgram() { + static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui_glow.vert"; + static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui_glow.frag"; + +#if LIVE_SHADER_RELOAD + static qint64 vsBuiltAge = 0; + static qint64 fsBuiltAge = 0; + QFileInfo vsInfo(vsFile); + QFileInfo fsInfo(fsFile); + auto vsAge = vsInfo.lastModified().toMSecsSinceEpoch(); + auto fsAge = fsInfo.lastModified().toMSecsSinceEpoch(); + if (!_overlayProgram || vsAge > vsBuiltAge || fsAge > fsBuiltAge) { + vsBuiltAge = vsAge; + fsBuiltAge = fsAge; +#else + if (!_overlayProgram) { +#endif + QString vsSource = readFile(vsFile); + QString fsSource = readFile(fsFile); + ProgramPtr program; + try { + compileProgram(program, vsSource.toLocal8Bit().toStdString(), fsSource.toLocal8Bit().toStdString()); + if (program) { + using namespace oglplus; + _overlayUniforms.mvp = Uniform(*program, "mvp").Location(); + _overlayUniforms.alpha = Uniform(*program, "alpha").Location(); + _overlayUniforms.glowColors = Uniform(*program, "glowColors").Location(); + _overlayUniforms.glowPoints = Uniform(*program, "glowPoints").Location(); + _overlayUniforms.resolution = Uniform(*program, "resolution").Location(); + _overlayUniforms.radius = Uniform(*program, "radius").Location(); + _overlayProgram = program; + useProgram(_overlayProgram); + Uniform(*_overlayProgram, _overlayUniforms.resolution).Set(CompositorHelper::VIRTUAL_SCREEN_SIZE); + } + } catch (std::runtime_error& error) { + qWarning() << "Error building overlay composite shader " << error.what(); + } + } } void HmdDisplayPlugin::uncustomizeContext() { + _overlayProgram.reset(); _sphereSection.reset(); _compositeFramebuffer.reset(); + _previewProgram.reset(); _reprojectionProgram.reset(); + _laserProgram.reset(); + _laserGeometry.reset(); Parent::uncustomizeContext(); } @@ -240,12 +276,12 @@ void HmdDisplayPlugin::compositeScene() { using namespace oglplus; Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); - Uniform(*_reprojectionProgram, REPROJECTION_MATRIX_LOCATION).Set(_currentPresentFrameInfo.presentReprojection); + Uniform(*_reprojectionProgram, _reprojectionUniforms.reprojectionMatrix).Set(_currentPresentFrameInfo.presentReprojection); //Uniform(*_reprojectionProgram, PROJECTION_MATRIX_LOCATION).Set(_eyeProjections); //Uniform(*_reprojectionProgram, INVERSE_PROJECTION_MATRIX_LOCATION).Set(_eyeInverseProjections); // FIXME what's the right oglplus mechanism to do this? It's not that ^^^ ... better yet, switch to a uniform buffer - glUniformMatrix4fv(INVERSE_PROJECTION_MATRIX_LOCATION, 2, GL_FALSE, &(_eyeInverseProjections[0][0][0])); - glUniformMatrix4fv(PROJECTION_MATRIX_LOCATION, 2, GL_FALSE, &(_eyeProjections[0][0][0])); + glUniformMatrix4fv(_reprojectionUniforms.inverseProjectionMatrix, 2, GL_FALSE, &(_eyeInverseProjections[0][0][0])); + glUniformMatrix4fv(_reprojectionUniforms.projectionMatrix, 2, GL_FALSE, &(_eyeProjections[0][0][0])); _plane->UseInProgram(*_reprojectionProgram); _plane->Draw(); } @@ -253,24 +289,95 @@ void HmdDisplayPlugin::compositeScene() { void HmdDisplayPlugin::compositeOverlay() { using namespace oglplus; auto compositorHelper = DependencyManager::get(); + glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix(); - // check the alpha - useProgram(_program); - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); + withPresentThreadLock([&] { + _presentHandLasers = _handLasers; + _presentHandPoses = _handPoses; + _presentUiModelTransform = _uiModelTransform; + }); + std::array handGlowPoints { { vec2(-1), vec2(-1) } }; - _sphereSection->Use(); - for_each_eye([&](Eye eye) { - eyeViewport(eye); - auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)); - auto mvp = _eyeProjections[eye] * modelView; - Uniform(*_program, _mvpUniform).Set(mvp); - _sphereSection->Draw(); - }); + // compute the glow point interesections + for (int i = 0; i < NUMBER_OF_HANDS; ++i) { + if (_presentHandPoses[i] == IDENTITY_MATRIX) { + continue; + } + const auto& handLaser = _presentHandLasers[i]; + if (!handLaser.valid()) { + continue; + } + + const auto& laserDirection = handLaser.direction; + auto model = _presentHandPoses[i]; + auto castDirection = glm::quat_cast(model) * laserDirection; + if (glm::abs(glm::length2(castDirection) - 1.0f) > EPSILON) { + castDirection = glm::normalize(castDirection); + castDirection = glm::inverse(_presentUiModelTransform.getRotation()) * castDirection; + } + + // FIXME fetch the actual UI radius from... somewhere? + float uiRadius = 1.0f; + + // Find the intersection of the laser with he UI and use it to scale the model matrix + float distance; + if (!glm::intersectRaySphere(vec3(_presentHandPoses[i][3]), castDirection, _presentUiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { + continue; + } + + vec3 intersectionPosition = vec3(_presentHandPoses[i][3]) + (castDirection * distance) - _presentUiModelTransform.getTranslation(); + intersectionPosition = glm::inverse(_presentUiModelTransform.getRotation()) * intersectionPosition; + + // Take the interesection normal and convert it to a texture coordinate + vec2 yawPitch; + { + vec2 xdir = glm::normalize(vec2(intersectionPosition.x, -intersectionPosition.z)); + yawPitch.x = glm::atan(xdir.x, xdir.y); + yawPitch.y = (acosf(intersectionPosition.y) * -1.0f) + M_PI_2; + } + vec2 halfFov = CompositorHelper::VIRTUAL_UI_TARGET_FOV / 2.0f; + + // Are we out of range + if (glm::any(glm::greaterThan(glm::abs(yawPitch), halfFov))) { + continue; + } + + yawPitch /= CompositorHelper::VIRTUAL_UI_TARGET_FOV; + yawPitch += 0.5f; + handGlowPoints[i] = yawPitch; } - Uniform(*_program, _alphaUniform).Set(1.0); + + updateOverlayProgram(); + if (!_overlayProgram) { + return; + } + + useProgram(_overlayProgram); + // Setup the uniforms + { + if (_overlayUniforms.alpha >= 0) { + Uniform(*_overlayProgram, _overlayUniforms.alpha).Set(_compositeOverlayAlpha); + } + if (_overlayUniforms.glowPoints >= 0) { + vec4 glowPoints(handGlowPoints[0], handGlowPoints[1]); + Uniform(*_overlayProgram, _overlayUniforms.glowPoints).Set(glowPoints); + } + if (_overlayUniforms.glowColors >= 0) { + std::array glowColors; + glowColors[0] = _presentHandLasers[0].color; + glowColors[1] = _presentHandLasers[1].color; + glProgramUniform4fv(GetName(*_overlayProgram), _overlayUniforms.glowColors, 2, &glowColors[0].r); + } + } + + _sphereSection->Use(); + for_each_eye([&](Eye eye) { + eyeViewport(eye); + auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat; + auto mvp = _eyeProjections[eye] * modelView; + Uniform(*_overlayProgram, _overlayUniforms.mvp).Set(mvp); + _sphereSection->Draw(); + }); } void HmdDisplayPlugin::compositePointer() { @@ -278,29 +385,27 @@ void HmdDisplayPlugin::compositePointer() { auto compositorHelper = DependencyManager::get(); - // check the alpha useProgram(_program); - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); + // set the alpha + Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); - // Mouse pointer - _plane->Use(); - // Reconstruct the headpose from the eye poses - auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]); - for_each_eye([&](Eye eye) { - eyeViewport(eye); - auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); - auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition); - auto mvp = _eyeProjections[eye] * reticleTransform; - Uniform(*_program, _mvpUniform).Set(mvp); - _plane->Draw(); - }); - } + // Mouse pointer + _plane->Use(); + // Reconstruct the headpose from the eye poses + auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]); + for_each_eye([&](Eye eye) { + eyeViewport(eye); + auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); + auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition); + auto mvp = _eyeProjections[eye] * reticleTransform; + Uniform(*_program, _mvpUniform).Set(mvp); + _plane->Draw(); + }); + // restore the alpha Uniform(*_program, _alphaUniform).Set(1.0); } + void HmdDisplayPlugin::internalPresent() { PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount()) @@ -309,30 +414,32 @@ void HmdDisplayPlugin::internalPresent() { hmdPresent(); // screen preview mirroring + auto window = _container->getPrimaryWidget(); + auto devicePixelRatio = window->devicePixelRatio(); + auto windowSize = toGlm(window->size()); + windowSize *= devicePixelRatio; + float windowAspect = aspect(windowSize); + float sceneAspect = _enablePreview ? aspect(_renderTargetSize) : _previewAspect; + if (_enablePreview && _monoPreview) { + sceneAspect /= 2.0f; + } + float aspectRatio = sceneAspect / windowAspect; + + uvec2 targetViewportSize = windowSize; + if (aspectRatio < 1.0f) { + targetViewportSize.x *= aspectRatio; + } else { + targetViewportSize.y /= aspectRatio; + } + + uvec2 targetViewportPosition; + if (targetViewportSize.x < windowSize.x) { + targetViewportPosition.x = (windowSize.x - targetViewportSize.x) / 2; + } else if (targetViewportSize.y < windowSize.y) { + targetViewportPosition.y = (windowSize.y - targetViewportSize.y) / 2; + } + if (_enablePreview) { - auto window = _container->getPrimaryWidget(); - auto windowSize = toGlm(window->size()); - float windowAspect = aspect(windowSize); - float sceneAspect = aspect(_renderTargetSize); - if (_monoPreview) { - sceneAspect /= 2.0f; - } - float aspectRatio = sceneAspect / windowAspect; - - uvec2 targetViewportSize = windowSize; - if (aspectRatio < 1.0f) { - targetViewportSize.x *= aspectRatio; - } else { - targetViewportSize.y /= aspectRatio; - } - - uvec2 targetViewportPosition; - if (targetViewportSize.x < windowSize.x) { - targetViewportPosition.x = (windowSize.x - targetViewportSize.x) / 2; - } else if (targetViewportSize.y < windowSize.y) { - targetViewportPosition.y = (windowSize.y - targetViewportSize.y) / 2; - } - using namespace oglplus; Context::Clear().ColorBuffer(); auto sourceSize = _compositeFramebuffer->size; @@ -347,6 +454,21 @@ void HmdDisplayPlugin::internalPresent() { BufferSelectBit::ColorBuffer, BlitFilter::Nearest); }); swapBuffers(); + } else if (_firstPreview || windowSize != _prevWindowSize || devicePixelRatio != _prevDevicePixelRatio) { + useProgram(_previewProgram); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(targetViewportPosition.x, targetViewportPosition.y, targetViewportSize.x, targetViewportSize.y); + glUniform1i(_previewUniforms.previewTexture, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, _previewTextureID); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + swapBuffers(); + _firstPreview = false; + _prevWindowSize = windowSize; + _prevDevicePixelRatio = devicePixelRatio; } postPreview(); @@ -357,22 +479,109 @@ void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm: void HmdDisplayPlugin::updateFrameData() { // Check if we have old frame data to discard - { - Lock lock(_mutex); + withPresentThreadLock([&] { auto itr = _frameInfos.find(_currentPresentFrameIndex); if (itr != _frameInfos.end()) { _frameInfos.erase(itr); } - } + }); Parent::updateFrameData(); - { - Lock lock(_mutex); + withPresentThreadLock([&] { _currentPresentFrameInfo = _frameInfos[_currentPresentFrameIndex]; - } + }); } glm::mat4 HmdDisplayPlugin::getHeadPose() const { return _currentRenderFrameInfo.renderPose; } + +bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) { + HandLaserInfo info; + info.mode = mode; + info.color = color; + info.direction = direction; + withRenderThreadLock([&] { + if (hands & Hand::LeftHand) { + _handLasers[0] = info; + } + if (hands & Hand::RightHand) { + _handLasers[1] = info; + } + }); + // FIXME defer to a child class plugin to determine if hand lasers are actually + // available based on the presence or absence of hand controllers + return true; +} + +void HmdDisplayPlugin::compositeExtra() { + // If neither hand laser is activated, exit + if (!_presentHandLasers[0].valid() && !_presentHandLasers[1].valid()) { + return; + } + + if (_presentHandPoses[0] == IDENTITY_MATRIX && _presentHandPoses[1] == IDENTITY_MATRIX) { + return; + } + + updateLaserProgram(); + + // Render hand lasers + using namespace oglplus; + useProgram(_laserProgram); + _laserGeometry->Use(); + std::array handLaserModelMatrices; + + for (int i = 0; i < NUMBER_OF_HANDS; ++i) { + if (_presentHandPoses[i] == IDENTITY_MATRIX) { + continue; + } + const auto& handLaser = _presentHandLasers[i]; + if (!handLaser.valid()) { + continue; + } + + const auto& laserDirection = handLaser.direction; + auto model = _presentHandPoses[i]; + auto castDirection = glm::quat_cast(model) * laserDirection; + if (glm::abs(glm::length2(castDirection) - 1.0f) > EPSILON) { + castDirection = glm::normalize(castDirection); + } + + // FIXME fetch the actual UI radius from... somewhere? + float uiRadius = 1.0f; + + // Find the intersection of the laser with he UI and use it to scale the model matrix + float distance; + if (!glm::intersectRaySphere(vec3(_presentHandPoses[i][3]), castDirection, _presentUiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { + continue; + } + + // Make sure we rotate to match the desired laser direction + if (laserDirection != Vectors::UNIT_NEG_Z) { + auto rotation = glm::rotation(Vectors::UNIT_NEG_Z, laserDirection); + model = model * glm::mat4_cast(rotation); + } + + model = glm::scale(model, vec3(distance)); + handLaserModelMatrices[i] = model; + } + + glEnable(GL_BLEND); + for_each_eye([&](Eye eye) { + eyeViewport(eye); + auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); + auto view = glm::inverse(eyePose); + const auto& projection = _eyeProjections[eye]; + for (int i = 0; i < NUMBER_OF_HANDS; ++i) { + if (handLaserModelMatrices[i] == IDENTITY_MATRIX) { + continue; + } + Uniform(*_laserProgram, "mvp").Set(projection * view * handLaserModelMatrices[i]); + Uniform(*_laserProgram, "color").Set(_presentHandLasers[i].color); + _laserGeometry->Draw(); + } + }); + glDisable(GL_BLEND); +} diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index e6ceb7e376..f168ec9607 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -10,6 +10,7 @@ #include #include +#include #include "../OpenGLDisplayPlugin.h" @@ -30,7 +31,7 @@ public: virtual glm::mat4 getHeadPose() const override; - + bool setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) override; protected: virtual void hmdPresent() = 0; @@ -39,6 +40,7 @@ protected: virtual void updatePresentPose(); bool internalActivate() override; + void internalDeactivate() override; void compositeScene() override; void compositeOverlay() override; void compositePointer() override; @@ -46,6 +48,26 @@ protected: void customizeContext() override; void uncustomizeContext() override; void updateFrameData() override; + void compositeExtra() override; + + struct HandLaserInfo { + HandLaserMode mode { HandLaserMode::None }; + vec4 color { 1.0f }; + vec3 direction { 0, 0, -1 }; + + // Is this hand laser info suitable for drawing? + bool valid() const { + return (mode != HandLaserMode::None && color.a > 0.0f && direction != vec3()); + } + }; + + Transform _uiModelTransform; + std::array _handLasers; + std::array _handPoses; + + Transform _presentUiModelTransform; + std::array _presentHandLasers; + std::array _presentHandPoses; std::array _eyeOffsets; std::array _eyeProjections; @@ -70,10 +92,49 @@ protected: FrameInfo _currentRenderFrameInfo; private: + void updateOverlayProgram(); + void updateLaserProgram(); + void updateReprojectionProgram(); + bool _enablePreview { false }; bool _monoPreview { true }; bool _enableReprojection { true }; - ShapeWrapperPtr _sphereSection; + bool _firstPreview { true }; + + ProgramPtr _overlayProgram; + struct OverlayUniforms { + int32_t mvp { -1 }; + int32_t alpha { -1 }; + int32_t glowColors { -1 }; + int32_t glowPoints { -1 }; + int32_t resolution { -1 }; + int32_t radius { -1 }; + } _overlayUniforms; + + ProgramPtr _previewProgram; + struct PreviewUniforms { + int32_t previewTexture { -1 }; + } _previewUniforms; + + float _previewAspect { 0 }; + GLuint _previewTextureID { 0 }; + glm::uvec2 _prevWindowSize { 0, 0 }; + qreal _prevDevicePixelRatio { 0 }; + ProgramPtr _reprojectionProgram; + struct ReprojectionUniforms { + int32_t reprojectionMatrix { -1 }; + int32_t inverseProjectionMatrix { -1 }; + int32_t projectionMatrix { -1 }; + } _reprojectionUniforms; + + ShapeWrapperPtr _sphereSection; + + ProgramPtr _laserProgram; + struct LaserUniforms { + int32_t mvp { -1 }; + int32_t color { -1 }; + } _laserUniforms; + ShapeWrapperPtr _laserGeometry; }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp index 5f55841be1..5d9f812edf 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp @@ -9,7 +9,7 @@ #include "SideBySideStereoDisplayPlugin.h" #include #include -#include +#include #include #include "../CompositorHelper.h" diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index 6c6716c8fa..cfdfb1fc21 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include #include "../CompositorHelper.h" diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index bb90c04c95..0063f4a701 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME entities-renderer) -AUTOSCRIBE_SHADER_LIB(gpu model render render-utils) +AUTOSCRIBE_SHADER_LIB(gpu model procedural render render-utils) setup_hifi_library(Widgets Network Script) link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index bec2fa9b8d..1ec934be92 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -29,17 +29,16 @@ #include "RenderableEntityItem.h" -#include "RenderableBoxEntityItem.h" #include "RenderableLightEntityItem.h" #include "RenderableModelEntityItem.h" #include "RenderableParticleEffectEntityItem.h" -#include "RenderableSphereEntityItem.h" #include "RenderableTextEntityItem.h" #include "RenderableWebEntityItem.h" #include "RenderableZoneEntityItem.h" #include "RenderableLineEntityItem.h" #include "RenderablePolyVoxEntityItem.h" #include "RenderablePolyLineEntityItem.h" +#include "RenderableShapeEntityItem.h" #include "EntitiesRendererLogging.h" #include "AddressManager.h" #include @@ -56,8 +55,6 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf _dontDoPrecisionPicking(false) { REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory) - REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory) - REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Web, RenderableWebEntityItem::factory) @@ -66,7 +63,10 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf REGISTER_ENTITY_TYPE_WITH_FACTORY(Line, RenderableLineEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(PolyVox, RenderablePolyVoxEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(PolyLine, RenderablePolyLineEntityItem::factory) - + REGISTER_ENTITY_TYPE_WITH_FACTORY(Shape, RenderableShapeEntityItem::factory) + REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableShapeEntityItem::boxFactory) + REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableShapeEntityItem::sphereFactory) + _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; _currentClickingOnEntityID = UNKNOWN_ENTITY_ID; } @@ -291,7 +291,9 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { foreach(const EntityItemID& entityID, _currentEntitiesInside) { if (!entitiesContainingAvatar.contains(entityID)) { emit leaveEntity(entityID); - _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } } } @@ -299,7 +301,9 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { foreach(const EntityItemID& entityID, entitiesContainingAvatar) { if (!_currentEntitiesInside.contains(entityID)) { emit enterEntity(entityID); - _entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity"); + } } } _currentEntitiesInside = entitiesContainingAvatar; @@ -315,7 +319,9 @@ void EntityTreeRenderer::leaveAllEntities() { // for all of our previous containing entities, if they are no longer containing then send them a leave event foreach(const EntityItemID& entityID, _currentEntitiesInside) { emit leaveEntity(entityID); - _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } } _currentEntitiesInside.clear(); forceRecheckEntities(); @@ -652,11 +658,15 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { } emit mousePressOnEntity(rayPickResult, event); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event)); + } _currentClickingOnEntityID = rayPickResult.entityID; emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event)); + } } else { emit mousePressOffEntity(rayPickResult, event); } @@ -677,14 +687,18 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { if (rayPickResult.intersects) { //qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; emit mouseReleaseOnEntity(rayPickResult, event); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event)); + } } // Even if we're no longer intersecting with an entity, if we started clicking on it, and now // we're releasing the button, then this is considered a clickOn event if (!_currentClickingOnEntityID.isInvalidID()) { emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event)); + } } // makes it the unknown ID, we just released so we can't be clicking on anything @@ -707,8 +721,10 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking); if (rayPickResult.intersects) { - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event)); + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event)); + } // handle the hover logic... @@ -716,19 +732,25 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // then we need to send the hover leave. if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + } } // If the new hover entity does not match the previous hover entity then we are entering the new one // this is true if the _currentHoverOverEntityID is known or unknown if (rayPickResult.entityID != _currentHoverOverEntityID) { - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event)); + } } // and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and // we should send our hover over event emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event)); + } // remember what we're hovering over _currentHoverOverEntityID = rayPickResult.entityID; @@ -739,7 +761,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // send the hover leave for our previous entity if (!_currentHoverOverEntityID.isInvalidID()) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + } _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID } } @@ -748,14 +772,16 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // not yet released the hold then this is still considered a holdingClickOnEntity event if (!_currentClickingOnEntityID.isInvalidID()) { emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event)); + } } _lastMouseEvent = MouseEvent(*event); _lastMouseEventValid = true; } void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { - if (_tree && !_shuttingDown) { + if (_tree && !_shuttingDown && _entitiesScriptEngine) { _entitiesScriptEngine->unloadEntityScript(entityID); } @@ -801,7 +827,7 @@ void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) { if (_tree && !_shuttingDown) { EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID); - if (entity && entity->shouldPreloadScript()) { + if (entity && entity->shouldPreloadScript() && _entitiesScriptEngine) { QString scriptUrl = entity->getScript(); scriptUrl = ResourceManager::normalizeURL(scriptUrl); ScriptEngine::loadEntityScript(_entitiesScriptEngine, entityID, scriptUrl, reload); @@ -910,12 +936,16 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons // And now the entity scripts if (isCollisionOwner(myNodeID, entityTree, idA, collision)) { emit collisionWithEntity(idA, idB, collision); - _entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision); + } } if (isCollisionOwner(myNodeID, entityTree, idA, collision)) { emit collisionWithEntity(idB, idA, collision); - _entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision); + } } } @@ -941,7 +971,9 @@ void EntityTreeRenderer::updateZone(const EntityItemID& id) { if (zone && zone->contains(_lastAvatarPosition)) { _currentEntitiesInside << id; emit enterEntity(id); - _entitiesScriptEngine->callEntityScriptMethod(id, "enterEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(id, "enterEntity"); + } if (zone->getVisible()) { _bestZone = std::dynamic_pointer_cast(zone); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 5c06c5f5cc..b0d0d2bacc 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -88,6 +88,8 @@ public: // For Scene.shouldRenderEntities QList& getEntitiesLastInScene() { return _entityIDsLastInScene; } + std::shared_ptr myAvatarZone() { return _bestZone; } + signals: void mousePressOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event); void mousePressOffEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event); diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp deleted file mode 100644 index e392450c08..0000000000 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// -// RenderableBoxEntityItem.cpp -// libraries/entities-renderer/src/ -// -// Created by Brad Hefta-Gaub on 8/6/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 "RenderableBoxEntityItem.h" - -#include - -#include - -#include -#include -#include - -#include -#include - -EntityItemPointer RenderableBoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity{ new RenderableBoxEntityItem(entityID) }; - entity->setProperties(properties); - return entity; -} - -void RenderableBoxEntityItem::setUserData(const QString& value) { - if (value != getUserData()) { - BoxEntityItem::setUserData(value); - if (_procedural) { - _procedural->parse(value); - } - } -} - -void RenderableBoxEntityItem::render(RenderArgs* args) { - PerformanceTimer perfTimer("RenderableBoxEntityItem::render"); - Q_ASSERT(getType() == EntityTypes::Box); - Q_ASSERT(args->_batch); - - if (!_procedural) { - _procedural.reset(new Procedural(this->getUserData())); - _procedural->_vertexSource = simple_vert; - _procedural->_fragmentSource = simple_frag; - _procedural->_state->setCullMode(gpu::State::CULL_NONE); - _procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL); - _procedural->_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); - } - - gpu::Batch& batch = *args->_batch; - glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha()); - - bool success; - auto transToCenter = getTransformToCenter(success); - if (!success) { - return; - } - - batch.setModelTransform(transToCenter); // we want to include the scale as well - if (_procedural->ready()) { - _procedural->prepare(batch, getPosition(), getDimensions()); - auto color = _procedural->getColor(cubeColor); - batch._glColor4f(color.r, color.g, color.b, color.a); - DependencyManager::get()->renderCube(batch); - } else { - DependencyManager::get()->renderSolidCubeInstance(batch, cubeColor); - } - static const auto triCount = DependencyManager::get()->getCubeTriangleCount(); - args->_details._trianglesRendered += (int)triCount; -} diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.h b/libraries/entities-renderer/src/RenderableBoxEntityItem.h deleted file mode 100644 index 67f881dbd8..0000000000 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// RenderableBoxEntityItem.h -// libraries/entities-renderer/src/ -// -// Created by Brad Hefta-Gaub on 8/6/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_RenderableBoxEntityItem_h -#define hifi_RenderableBoxEntityItem_h - -#include -#include - -#include "RenderableEntityItem.h" - -class RenderableBoxEntityItem : public BoxEntityItem { -public: - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - RenderableBoxEntityItem(const EntityItemID& entityItemID) : BoxEntityItem(entityItemID) { } - - virtual void render(RenderArgs* args) override; - virtual void setUserData(const QString& value) override; - - SIMPLE_RENDERABLE() -private: - QSharedPointer _procedural; -}; - - -#endif // hifi_RenderableBoxEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index d148145dde..011675fc82 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -47,6 +47,9 @@ namespace render { } void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status::Getters& statusGetters) { + auto nodeList = DependencyManager::get(); + const QUuid& myNodeID = nodeList->getSessionUUID(); + statusGetters.push_back([entity] () -> render::Item::Status::Value { quint64 delta = usecTimestampNow() - entity->getLastEditedFromRemote(); const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND); @@ -81,9 +84,7 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status: (unsigned char)RenderItemStatusIcon::ACTIVE_IN_BULLET); }); - statusGetters.push_back([entity] () -> render::Item::Status::Value { - auto nodeList = DependencyManager::get(); - const QUuid& myNodeID = nodeList->getSessionUUID(); + statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value { bool weOwnSimulation = entity->getSimulationOwner().matchesValidID(myNodeID); bool otherOwnSimulation = !weOwnSimulation && !entity->getSimulationOwner().isNull(); @@ -106,4 +107,18 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status: return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN, (unsigned char)RenderItemStatusIcon::HAS_ACTIONS); }); + + statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value { + if (entity->getClientOnly()) { + if (entity->getOwningAvatarID() == myNodeID) { + return render::Item::Status::Value(1.0f, render::Item::Status::Value::GREEN, + (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); + } else { + return render::Item::Status::Value(1.0f, render::Item::Status::Value::RED, + (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); + } + } + return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN, + (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); + }); } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 09451e87d4..9840bf3150 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -26,6 +26,7 @@ enum class RenderItemStatusIcon { SIMULATION_OWNER = 3, HAS_ACTIONS = 4, OTHER_SIMULATION_OWNER = 5, + CLIENT_ONLY = 6, NONE = 255 }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index a537ecd0f3..bef790299c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include @@ -48,13 +49,6 @@ RenderableModelEntityItem::~RenderableModelEntityItem() { void RenderableModelEntityItem::setModelURL(const QString& url) { auto& currentURL = getParsedModelURL(); - if (_model && (currentURL != url)) { - // The machinery for updateModelBounds will give existing models the opportunity to fix their translation/rotation/scale/registration. - // The first two are straightforward, but the latter two have guards to make sure they don't happen after they've already been set. - // Here we reset those guards. This doesn't cause the entity values to change -- it just allows the model to match once it comes in. - _model->setScaleToFit(false, getDimensions()); - _model->setSnapModelToRegistrationPoint(false, getRegistrationPoint()); - } ModelEntityItem::setModelURL(url); if (currentURL != getParsedModelURL() || !_model) { @@ -163,6 +157,14 @@ void RenderableModelEntityItem::remapTextures() { } void RenderableModelEntityItem::doInitialModelSimulation() { + // The machinery for updateModelBounds will give existing models the opportunity to fix their + // translation/rotation/scale/registration. The first two are straightforward, but the latter two have guards to + // make sure they don't happen after they've already been set. Here we reset those guards. This doesn't cause the + // entity values to change -- it just allows the model to match once it comes in. + _model->setScaleToFit(false, getDimensions()); + _model->setSnapModelToRegistrationPoint(false, getRegistrationPoint()); + + // now recalculate the bounds and registration _model->setScaleToFit(true, getDimensions()); _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); _model->setRotation(getRotation()); @@ -341,16 +343,23 @@ void RenderableModelEntityItem::updateModelBounds() { if (!hasModel() || !_model) { return; } + if (!_dimensionsInitialized || !_model->isActive()) { + return; + } + + bool movingOrAnimating = isMovingRelativeToParent() || isAnimatingSomething(); glm::vec3 dimensions = getDimensions(); - if ((movingOrAnimating || + bool success; + auto transform = getTransform(success); + + if (movingOrAnimating || _needsInitialSimulation || _needsJointSimulation || - _model->getTranslation() != getPosition() || + _model->getTranslation() != transform.getTranslation() || _model->getScaleToFitDimensions() != dimensions || - _model->getRotation() != getRotation() || - _model->getRegistrationPoint() != getRegistrationPoint()) - && _model->isActive() && _dimensionsInitialized) { + _model->getRotation() != transform.getRotation() || + _model->getRegistrationPoint() != getRegistrationPoint()) { doInitialModelSimulation(); _needsJointSimulation = false; } @@ -422,7 +431,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) { // check to see if when we added our models to the scene they were ready, if they were not ready, then // fix them up in the scene - bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0; + bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0 + && getShapeType() == SHAPE_TYPE_COMPOUND; if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) { _showCollisionHull = shouldShowCollisionHull; render::PendingChanges pendingChanges; @@ -592,44 +602,44 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { // the model is still being downloaded. return false; + } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { + return (_model && _model->isLoaded()); } return true; } void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { ShapeType type = getShapeType(); - if (type != SHAPE_TYPE_COMPOUND) { - ModelEntityItem::computeShapeInfo(info); - info.setParams(type, 0.5f * getDimensions()); - adjustShapeInfoByRegistration(info); - } else { + glm::vec3 dimensions = getDimensions(); + if (type == SHAPE_TYPE_COMPOUND) { updateModelBounds(); // should never fall in here when collision model not fully loaded // hence we assert that all geometries exist and are loaded - assert(_model->isLoaded() && _model->isCollisionLoaded()); - const FBXGeometry& renderGeometry = _model->getFBXGeometry(); + assert(_model && _model->isLoaded() && _model->isCollisionLoaded()); const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry(); - _points.clear(); - unsigned int i = 0; + ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); + pointCollection.clear(); + uint32_t i = 0; // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. + const uint32_t TRIANGLE_STRIDE = 3; + const uint32_t QUAD_STRIDE = 4; foreach (const FBXMesh& mesh, collisionGeometry.meshes) { // each meshPart is a convex hull foreach (const FBXMeshPart &meshPart, mesh.parts) { - QVector pointsInPart; + pointCollection.push_back(QVector()); + ShapeInfo::PointList& pointsInPart = pointCollection[i]; // run through all the triangles and (uniquely) add each point to the hull - unsigned int triangleCount = meshPart.triangleIndices.size() / 3; - for (unsigned int j = 0; j < triangleCount; j++) { - unsigned int p0Index = meshPart.triangleIndices[j*3]; - unsigned int p1Index = meshPart.triangleIndices[j*3+1]; - unsigned int p2Index = meshPart.triangleIndices[j*3+2]; - glm::vec3 p0 = mesh.vertices[p0Index]; - glm::vec3 p1 = mesh.vertices[p1Index]; - glm::vec3 p2 = mesh.vertices[p2Index]; + uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); + assert(numIndices % TRIANGLE_STRIDE == 0); + for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]]; if (!pointsInPart.contains(p0)) { pointsInPart << p0; } @@ -642,17 +652,13 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } // run through all the quads and (uniquely) add each point to the hull - unsigned int quadCount = meshPart.quadIndices.size() / 4; - assert((unsigned int)meshPart.quadIndices.size() == quadCount*4); - for (unsigned int j = 0; j < quadCount; j++) { - unsigned int p0Index = meshPart.quadIndices[j*4]; - unsigned int p1Index = meshPart.quadIndices[j*4+1]; - unsigned int p2Index = meshPart.quadIndices[j*4+2]; - unsigned int p3Index = meshPart.quadIndices[j*4+3]; - glm::vec3 p0 = mesh.vertices[p0Index]; - glm::vec3 p1 = mesh.vertices[p1Index]; - glm::vec3 p2 = mesh.vertices[p2Index]; - glm::vec3 p3 = mesh.vertices[p3Index]; + numIndices = (uint32_t)meshPart.quadIndices.size(); + assert(numIndices % QUAD_STRIDE == 0); + for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.quadIndices[j + 2]]; + glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]]; if (!pointsInPart.contains(p0)) { pointsInPart << p0; } @@ -669,14 +675,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { if (pointsInPart.size() == 0) { qCDebug(entitiesrenderer) << "Warning -- meshPart has no faces"; + pointCollection.pop_back(); continue; } - - // add next convex hull - QVector newMeshPoints; - _points << newMeshPoints; - // add points to the new convex hull - _points[i++] << pointsInPart; + ++i; } } @@ -686,27 +688,224 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // to the visual model and apply them to the collision model (without regard for the // collision model's extents). - glm::vec3 scale = getDimensions() / renderGeometry.getUnscaledMeshExtents().size(); + glm::vec3 scaleToFit = dimensions / _model->getFBXGeometry().getUnscaledMeshExtents().size(); // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. - AABox box; - for (int i = 0; i < _points.size(); i++) { - for (int j = 0; j < _points[i].size(); j++) { + for (int32_t i = 0; i < pointCollection.size(); i++) { + for (int32_t j = 0; j < pointCollection[i].size(); j++) { // compensate for registration - _points[i][j] += _model->getOffset(); + pointCollection[i][j] += _model->getOffset(); // scale so the collision points match the model points - _points[i][j] *= scale; - // this next subtraction is done so we can give info the offset, which will cause - // the shape-key to change. - _points[i][j] -= _model->getOffset(); - box += _points[i][j]; + pointCollection[i][j] *= scaleToFit; + } + } + info.setParams(type, dimensions, _compoundShapeURL); + } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { + updateModelBounds(); + + // should never fall in here when model not fully loaded + assert(_model && _model->isLoaded()); + + // compute meshPart local transforms + QVector localTransforms; + const FBXGeometry& fbxGeometry = _model->getFBXGeometry(); + int numFbxMeshes = fbxGeometry.meshes.size(); + int totalNumVertices = 0; + for (int i = 0; i < numFbxMeshes; i++) { + const FBXMesh& mesh = fbxGeometry.meshes.at(i); + if (mesh.clusters.size() > 0) { + const FBXCluster& cluster = mesh.clusters.at(0); + auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex); + localTransforms.push_back(jointMatrix * cluster.inverseBindMatrix); + } else { + glm::mat4 identity; + localTransforms.push_back(identity); + } + totalNumVertices += mesh.vertices.size(); + } + const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; + if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { + qWarning() << "model" << getModelURL() << "has too many vertices" << totalNumVertices << "and will collide as a box."; + info.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); + return; + } + + auto& meshes = _model->getGeometry()->getMeshes(); + int32_t numMeshes = (int32_t)(meshes.size()); + + ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); + pointCollection.clear(); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + pointCollection.resize(numMeshes); + } else { + pointCollection.resize(1); + } + + Extents extents; + int32_t meshCount = 0; + int32_t pointListIndex = 0; + for (auto& mesh : meshes) { + const gpu::BufferView& vertices = mesh->getVertexBuffer(); + const gpu::BufferView& indices = mesh->getIndexBuffer(); + const gpu::BufferView& parts = mesh->getPartBuffer(); + + ShapeInfo::PointList& points = pointCollection[pointListIndex]; + + // reserve room + int32_t sizeToReserve = (int32_t)(vertices.getNumElements()); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // a list of points for each mesh + pointListIndex++; + } else { + // only one list of points + sizeToReserve += (int32_t)((gpu::Size)points.size()); + } + points.reserve(sizeToReserve); + + // copy points + uint32_t meshIndexOffset = (uint32_t)points.size(); + const glm::mat4& localTransform = localTransforms[meshCount]; + gpu::BufferView::Iterator vertexItr = vertices.cbegin(); + while (vertexItr != vertices.cend()) { + glm::vec3 point = extractTranslation(localTransform * glm::translate(*vertexItr)); + points.push_back(point); + extents.addPoint(point); + ++vertexItr; + } + + if (type == SHAPE_TYPE_STATIC_MESH) { + // copy into triangleIndices + ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); + triangleIndices.clear(); + triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements())); + gpu::BufferView::Iterator partItr = parts.cbegin(); + while (partItr != parts.cend()) { + if (partItr->_topology == model::Mesh::TRIANGLES) { + assert(partItr->_numIndices % 3 == 0); + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + partItr->_numIndices; + while (indexItr != indexEnd) { + triangleIndices.push_back(*indexItr + meshIndexOffset); + ++indexItr; + } + } else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) { + assert(partItr->_numIndices > 2); + uint32_t approxNumIndices = 3 * partItr->_numIndices; + if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) { + // we underestimated the final size of triangleIndices so we pre-emptively expand it + triangleIndices.reserve(triangleIndices.size() + approxNumIndices); + } + + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + (partItr->_numIndices - 2); + + // first triangle uses the first three indices + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); + + // the rest use previous and next index + uint32_t triangleCount = 1; + while (indexItr != indexEnd) { + if ((*indexItr) != model::Mesh::PRIMITIVE_RESTART_INDEX) { + if (triangleCount % 2 == 0) { + // even triangles use first two indices in order + triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); + triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); + } else { + // odd triangles swap order of first two indices + triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); + triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); + } + triangleIndices.push_back(*indexItr + meshIndexOffset); + ++triangleCount; + } + ++indexItr; + } + } + ++partItr; + } + } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // for each mesh copy unique part indices, separated by special bogus (flag) index values + ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); + triangleIndices.clear(); + gpu::BufferView::Iterator partItr = parts.cbegin(); + while (partItr != parts.cend()) { + // collect unique list of indices for this part + std::set uniqueIndices; + if (partItr->_topology == model::Mesh::TRIANGLES) { + assert(partItr->_numIndices % 3 == 0); + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + partItr->_numIndices; + while (indexItr != indexEnd) { + uniqueIndices.insert(*indexItr); + ++indexItr; + } + } else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) { + assert(partItr->_numIndices > 2); + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + (partItr->_numIndices - 2); + + // first triangle uses the first three indices + uniqueIndices.insert(*(indexItr++)); + uniqueIndices.insert(*(indexItr++)); + uniqueIndices.insert(*(indexItr++)); + + // the rest use previous and next index + uint32_t triangleCount = 1; + while (indexItr != indexEnd) { + if ((*indexItr) != model::Mesh::PRIMITIVE_RESTART_INDEX) { + if (triangleCount % 2 == 0) { + // even triangles use first two indices in order + uniqueIndices.insert(*(indexItr - 2)); + uniqueIndices.insert(*(indexItr - 1)); + } else { + // odd triangles swap order of first two indices + uniqueIndices.insert(*(indexItr - 1)); + uniqueIndices.insert(*(indexItr - 2)); + } + uniqueIndices.insert(*indexItr); + ++triangleCount; + } + ++indexItr; + } + } + + // store uniqueIndices in triangleIndices + triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size()); + for (auto index : uniqueIndices) { + triangleIndices.push_back(index); + } + // flag end of part + triangleIndices.push_back(END_OF_MESH_PART); + + ++partItr; + } + // flag end of mesh + triangleIndices.push_back(END_OF_MESH); + } + ++meshCount; + } + + // scale and shift + glm::vec3 extentsSize = extents.size(); + glm::vec3 scaleToFit = dimensions / extentsSize; + for (int32_t i = 0; i < 3; ++i) { + if (extentsSize[i] < 1.0e-6f) { + scaleToFit[i] = 1.0f; + } + } + for (auto points : pointCollection) { + for (int32_t i = 0; i < points.size(); ++i) { + points[i] = (points[i] * scaleToFit); } } - glm::vec3 collisionModelDimensions = box.getDimensions(); - info.setParams(type, collisionModelDimensions, _compoundShapeURL); - info.setConvexHulls(_points); - info.setOffset(_model->getOffset()); + info.setParams(type, 0.5f * dimensions, _modelURL); + } else { + ModelEntityItem::computeShapeInfo(info); + info.setParams(type, 0.5f * dimensions); + adjustShapeInfoByRegistration(info); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index d2de45f538..339c907532 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -103,7 +103,6 @@ private: QVariantMap _currentTextures; QVariantMap _originalTextures; bool _originalTexturesRead = false; - QVector> _points; bool _dimensionsInitialized = true; AnimationPropertyGroup _renderAnimationProperties; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 1d5570b8b7..eb6db2874f 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -981,7 +981,7 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() { PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast(simulation); EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; if (packetSender) { - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, tree, entity->getID(), properties); } }); }); @@ -1198,7 +1198,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] { auto polyVoxEntity = std::static_pointer_cast(entity); - QVector> points; + QVector> pointCollection; AABox box; glm::mat4 vtoM = std::static_pointer_cast(entity)->voxelToLocalMatrix(); @@ -1207,7 +1207,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { // pull each triangle in the mesh into a polyhedron which can be collided with unsigned int i = 0; - const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); + const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); gpu::BufferView::Iterator it = indexBufferView.cbegin(); @@ -1241,9 +1241,9 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { pointsInPart << p3Model; // add next convex hull QVector newMeshPoints; - points << newMeshPoints; + pointCollection << newMeshPoints; // add points to the new convex hull - points[i++] << pointsInPart; + pointCollection[i++] << pointsInPart; } } else { unsigned int i = 0; @@ -1299,19 +1299,19 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { // add next convex hull QVector newMeshPoints; - points << newMeshPoints; + pointCollection << newMeshPoints; // add points to the new convex hull - points[i++] << pointsInPart; + pointCollection[i++] << pointsInPart; } }); } - polyVoxEntity->setCollisionPoints(points, box); + polyVoxEntity->setCollisionPoints(pointCollection, box); }); } -void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector> points, AABox box) { +void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection pointCollection, AABox box) { // this catches the payload from computeShapeInfoWorker - if (points.isEmpty()) { + if (pointCollection.isEmpty()) { EntityItem::computeShapeInfo(_shapeInfo); return; } @@ -1325,7 +1325,7 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector thunk); void setMesh(model::MeshPointer mesh); - void setCollisionPoints(const QVector> points, AABox box); + void setCollisionPoints(ShapeInfo::PointCollection points, AABox box); PolyVox::SimpleVolume* getVolData() { return _volData; } uint8_t getVoxelInternal(int x, int y, int z); diff --git a/libraries/entities-renderer/src/RenderableProceduralItemShader.h b/libraries/entities-renderer/src/RenderableProceduralItemShader.h deleted file mode 100644 index 1afd3bc608..0000000000 --- a/libraries/entities-renderer/src/RenderableProceduralItemShader.h +++ /dev/null @@ -1,361 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/09/05 -// 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 -// - -// Shader includes portions of webgl-noise: -// Description : Array and textureless GLSL 2D/3D/4D simplex -// noise functions. -// Author : Ian McEwan, Ashima Arts. -// Maintainer : ijm -// Lastmod : 20110822 (ijm) -// License : Copyright (C) 2011 Ashima Arts. All rights reserved. -// Distributed under the MIT License. See LICENSE file. -// https://github.com/ashima/webgl-noise -// - - -const QString SHADER_COMMON = R"SHADER( -layout(location = 0) out vec4 _fragColor0; -layout(location = 1) out vec4 _fragColor1; -layout(location = 2) out vec4 _fragColor2; - -// the alpha threshold -uniform float alphaThreshold; -uniform sampler2D normalFittingMap; - -vec3 bestFitNormal(vec3 normal) { - vec3 absNorm = abs(normal); - float maxNAbs = max(absNorm.z, max(absNorm.x, absNorm.y)); - - vec2 texcoord = (absNorm.z < maxNAbs ? - (absNorm.y < maxNAbs ? absNorm.yz : absNorm.xz) : - absNorm.xy); - texcoord = (texcoord.x < texcoord.y ? texcoord.yx : texcoord.xy); - texcoord.y /= texcoord.x; - vec3 cN = normal / maxNAbs; - float fittingScale = texture(normalFittingMap, texcoord).a; - cN *= fittingScale; - return (cN * 0.5 + 0.5); -} - - - -float mod289(float x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -vec2 mod289(vec2 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -vec3 mod289(vec3 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -vec4 mod289(vec4 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -float permute(float x) { - return mod289(((x*34.0)+1.0)*x); -} - -vec3 permute(vec3 x) { - return mod289(((x*34.0)+1.0)*x); -} - -vec4 permute(vec4 x) { - return mod289(((x*34.0)+1.0)*x); -} - -float taylorInvSqrt(float r) { - return 1.79284291400159 - 0.85373472095314 * r; -} - -vec4 taylorInvSqrt(vec4 r) { - return 1.79284291400159 - 0.85373472095314 * r; -} - -vec4 grad4(float j, vec4 ip) { - const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0); - vec4 p, s; - - p.xyz = floor(fract(vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0; - p.w = 1.5 - dot(abs(p.xyz), ones.xyz); - s = vec4(lessThan(p, vec4(0.0))); - p.xyz = p.xyz + (s.xyz * 2.0 - 1.0) * s.www; - - return p; -} - -// (sqrt(5) - 1)/4 = F4, used once below -#define F4 0.309016994374947451 - -float snoise(vec4 v) { - const vec4 C = vec4(0.138196601125011, // (5 - sqrt(5))/20 G4 - 0.276393202250021, // 2 * G4 - 0.414589803375032, // 3 * G4 - -0.447213595499958); // -1 + 4 * G4 - - // First corner - vec4 i = floor(v + dot(v, vec4(F4))); - vec4 x0 = v - i + dot(i, C.xxxx); - - // Other corners - - // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) - vec4 i0; - vec3 isX = step(x0.yzw, x0.xxx); - vec3 isYZ = step(x0.zww, x0.yyz); - i0.x = isX.x + isX.y + isX.z; - i0.yzw = 1.0 - isX; - i0.y += isYZ.x + isYZ.y; - i0.zw += 1.0 - isYZ.xy; - i0.z += isYZ.z; - i0.w += 1.0 - isYZ.z; - - // i0 now contains the unique values 0,1,2,3 in each channel - vec4 i3 = clamp(i0, 0.0, 1.0); - vec4 i2 = clamp(i0 - 1.0, 0.0, 1.0); - vec4 i1 = clamp(i0 - 2.0, 0.0, 1.0); - - vec4 x1 = x0 - i1 + C.xxxx; - vec4 x2 = x0 - i2 + C.yyyy; - vec4 x3 = x0 - i3 + C.zzzz; - vec4 x4 = x0 + C.wwww; - - // Permutations - i = mod289(i); - float j0 = permute(permute(permute(permute(i.w) + i.z) + i.y) + i.x); - vec4 j1 = permute( - permute( - permute( - permute(i.w + vec4(i1.w, i2.w, i3.w, 1.0)) + i.z - + vec4(i1.z, i2.z, i3.z, 1.0)) + i.y - + vec4(i1.y, i2.y, i3.y, 1.0)) + i.x - + vec4(i1.x, i2.x, i3.x, 1.0)); - - // Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope - // 7*7*6 = 294, which is close to the ring size 17*17 = 289. - vec4 ip = vec4(1.0 / 294.0, 1.0 / 49.0, 1.0 / 7.0, 0.0); - - vec4 p0 = grad4(j0, ip); - vec4 p1 = grad4(j1.x, ip); - vec4 p2 = grad4(j1.y, ip); - vec4 p3 = grad4(j1.z, ip); - vec4 p4 = grad4(j1.w, ip); - - // Normalise gradients - vec4 norm = taylorInvSqrt( - vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); - p0 *= norm.x; - p1 *= norm.y; - p2 *= norm.z; - p3 *= norm.w; - p4 *= taylorInvSqrt(dot(p4, p4)); - - // Mix contributions from the five corners - vec3 m0 = max(0.6 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2)), 0.0); - vec2 m1 = max(0.6 - vec2(dot(x3, x3), dot(x4, x4)), 0.0); - m0 = m0 * m0; - m1 = m1 * m1; - return 49.0 - * (dot(m0 * m0, vec3(dot(p0, x0), dot(p1, x1), dot(p2, x2))) - + dot(m1 * m1, vec2(dot(p3, x3), dot(p4, x4)))); - -} - -float snoise(vec3 v) { - const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); - const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); - - // First corner - vec3 i = floor(v + dot(v, C.yyy)); - vec3 x0 = v - i + dot(i, C.xxx); - - // Other corners - vec3 g = step(x0.yzx, x0.xyz); - vec3 l = 1.0 - g; - vec3 i1 = min(g.xyz, l.zxy); - vec3 i2 = max(g.xyz, l.zxy); - - vec3 x1 = x0 - i1 + C.xxx; - vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y - vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y - - // Permutations - i = mod289(i); - vec4 p = permute( - permute( - permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y - + vec4(0.0, i1.y, i2.y, 1.0)) + i.x - + vec4(0.0, i1.x, i2.x, 1.0)); - - // Gradients: 7x7 points over a square, mapped onto an octahedron. - // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) - float n_ = 0.142857142857; // 1.0/7.0 - vec3 ns = n_ * D.wyz - D.xzx; - - vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) - - vec4 x_ = floor(j * ns.z); - vec4 y_ = floor(j - 7.0 * x_); // mod(j,N) - - vec4 x = x_ * ns.x + ns.yyyy; - vec4 y = y_ * ns.x + ns.yyyy; - vec4 h = 1.0 - abs(x) - abs(y); - - vec4 b0 = vec4(x.xy, y.xy); - vec4 b1 = vec4(x.zw, y.zw); - - //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; - //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; - vec4 s0 = floor(b0) * 2.0 + 1.0; - vec4 s1 = floor(b1) * 2.0 + 1.0; - vec4 sh = -step(h, vec4(0.0)); - - vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; - vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; - - vec3 p0 = vec3(a0.xy, h.x); - vec3 p1 = vec3(a0.zw, h.y); - vec3 p2 = vec3(a1.xy, h.z); - vec3 p3 = vec3(a1.zw, h.w); - - //Normalise gradients - vec4 norm = taylorInvSqrt( - vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); - p0 *= norm.x; - p1 *= norm.y; - p2 *= norm.z; - p3 *= norm.w; - - // Mix final noise value - vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), - 0.0); - m = m * m; - return 42.0 - * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))); -} - -float snoise(vec2 v) { - const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 - 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) - -0.577350269189626, // -1.0 + 2.0 * C.x - 0.024390243902439); // 1.0 / 41.0 - // First corner - vec2 i = floor(v + dot(v, C.yy)); - vec2 x0 = v - i + dot(i, C.xx); - - // Other corners - vec2 i1; - i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); - vec4 x12 = x0.xyxy + C.xxzz; - x12.xy -= i1; - - // Permutations - i = mod289(i); // Avoid truncation effects in permutation - vec3 p = permute( - permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0)); - - vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), - 0.0); - m = m * m; - m = m * m; - - // Gradients: 41 points uniformly over a line, mapped onto a diamond. - // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) - - vec3 x = 2.0 * fract(p * C.www) - 1.0; - vec3 h = abs(x) - 0.5; - vec3 ox = floor(x + 0.5); - vec3 a0 = x - ox; - - // Normalise gradients implicitly by scaling m - // Approximation of: m *= inversesqrt( a0*a0 + h*h ); - m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); - - // Compute final noise value at P - vec3 g; - g.x = a0.x * x0.x + h.x * x0.y; - g.yz = a0.yz * x12.xz + h.yz * x12.yw; - return 130.0 * dot(m, g); -} - -// the interpolated normal -in vec3 _normal; -in vec3 _color; -in vec2 _texCoord0; -in vec4 _position; - -// TODO add more uniforms -uniform float iGlobalTime; // shader playback time (in seconds) -uniform vec3 iWorldScale; // the dimensions of the object being rendered - -// TODO add support for textures -// TODO document available inputs other than the uniforms -// TODO provide world scale in addition to the untransformed position - -const vec3 DEFAULT_SPECULAR = vec3(0.1); -const float DEFAULT_SHININESS = 10; - -)SHADER"; - -// V1 shaders, only support emissive -// vec4 getProceduralColor() -const QString SHADER_TEMPLATE_V1 = SHADER_COMMON + R"SCRIBE( - -#line 1001 -%1 -#line 317 - -void main(void) { - vec4 emissive = getProceduralColor(); - - float alpha = emissive.a; - if (alpha != 1.0) { - discard; - } - - vec4 diffuse = vec4(_color.rgb, alpha); - vec4 normal = vec4(normalize(bestFitNormal(_normal)), 0.5); - - _fragColor0 = diffuse; - _fragColor1 = normal; - _fragColor2 = vec4(emissive.rgb, DEFAULT_SHININESS / 128.0); -} - -)SCRIBE"; - -// void getProceduralDiffuseAndEmissive(out vec4 diffuse, out vec4 emissive) -const QString SHADER_TEMPLATE_V2 = SHADER_COMMON + R"SCRIBE( -// FIXME should we be doing the swizzle here? -vec3 iResolution = iWorldScale.xzy; - -// FIXME Mouse X,Y coordinates, and Z,W are for the click position if clicked (not supported in High Fidelity at the moment) -vec4 iMouse = vec4(0); - -// FIXME We set the seconds (iDate.w) of iDate to iGlobalTime, which contains the current date in seconds -vec4 iDate = vec4(0, 0, 0, iGlobalTime); - - -#line 1001 -%1 -#line 351 - -void main(void) { - vec3 diffuse = _color.rgb; - vec3 specular = DEFAULT_SPECULAR; - float shininess = DEFAULT_SHININESS; - - float emissiveAmount = getProceduralColors(diffuse, specular, shininess); - - _fragColor0 = vec4(diffuse.rgb, 1.0); - _fragColor1 = vec4(bestFitNormal(normalize(_normal.xyz)), 1.0 - (emissiveAmount / 2.0)); - _fragColor2 = vec4(specular, shininess / 128.0); -} -)SCRIBE"; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp new file mode 100644 index 0000000000..48ad05a714 --- /dev/null +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -0,0 +1,115 @@ +// +// Created by Bradley Austin Davis on 2016/05/09 +// 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 "RenderableShapeEntityItem.h" + +#include + +#include + +#include +#include +#include + +#include +#include + +// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 +// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. +static const float SPHERE_ENTITY_SCALE = 0.5f; + +static std::array MAPPING { { + GeometryCache::Triangle, + GeometryCache::Quad, + GeometryCache::Hexagon, + GeometryCache::Octagon, + GeometryCache::Circle, + GeometryCache::Cube, + GeometryCache::Sphere, + GeometryCache::Tetrahedron, + GeometryCache::Octahedron, + GeometryCache::Dodecahedron, + GeometryCache::Icosahedron, + GeometryCache::Torus, + GeometryCache::Cone, + GeometryCache::Cylinder, +} }; + + +RenderableShapeEntityItem::Pointer RenderableShapeEntityItem::baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + Pointer entity = std::make_shared(entityID); + entity->setProperties(properties); + return entity; +} + +EntityItemPointer RenderableShapeEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return baseFactory(entityID, properties); +} + +EntityItemPointer RenderableShapeEntityItem::boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + auto result = baseFactory(entityID, properties); + result->setShape(entity::Cube); + return result; +} + +EntityItemPointer RenderableShapeEntityItem::sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + auto result = baseFactory(entityID, properties); + result->setShape(entity::Sphere); + return result; +} + +void RenderableShapeEntityItem::setUserData(const QString& value) { + if (value != getUserData()) { + ShapeEntityItem::setUserData(value); + if (_procedural) { + _procedural->parse(value); + } + } +} + +void RenderableShapeEntityItem::render(RenderArgs* args) { + PerformanceTimer perfTimer("RenderableShapeEntityItem::render"); + //Q_ASSERT(getType() == EntityTypes::Shape); + Q_ASSERT(args->_batch); + + if (!_procedural) { + _procedural.reset(new Procedural(getUserData())); + _procedural->_vertexSource = simple_vert; + _procedural->_fragmentSource = simple_frag; + _procedural->_state->setCullMode(gpu::State::CULL_NONE); + _procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL); + _procedural->_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); + } + + gpu::Batch& batch = *args->_batch; + glm::vec4 color(toGlm(getXColor()), getLocalRenderAlpha()); + bool success; + Transform modelTransform = getTransformToCenter(success); + if (!success) { + return; + } + if (_shape == entity::Sphere) { + modelTransform.postScale(SPHERE_ENTITY_SCALE); + } + batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation + if (_procedural->ready()) { + _procedural->prepare(batch, getPosition(), getDimensions(), getOrientation()); + auto outColor = _procedural->getColor(color); + batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); + DependencyManager::get()->renderShape(batch, MAPPING[_shape]); + } else { + // FIXME, support instanced multi-shape rendering using multidraw indirect + DependencyManager::get()->renderSolidShapeInstance(batch, MAPPING[_shape], color); + } + + + static const auto triCount = DependencyManager::get()->getShapeTriangleCount(MAPPING[_shape]); + args->_details._trianglesRendered += (int)triCount; +} diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h new file mode 100644 index 0000000000..b18370b13c --- /dev/null +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -0,0 +1,36 @@ +// +// Created by Bradley Austin Davis on 2016/05/09 +// 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_RenderableShapeEntityItem_h +#define hifi_RenderableShapeEntityItem_h + +#include +#include + +#include "RenderableEntityItem.h" + +class RenderableShapeEntityItem : public ShapeEntityItem { + using Pointer = std::shared_ptr; + static Pointer baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties); +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + static EntityItemPointer boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties); + static EntityItemPointer sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties); + RenderableShapeEntityItem(const EntityItemID& entityItemID) : ShapeEntityItem(entityItemID) {} + + void render(RenderArgs* args) override; + void setUserData(const QString& value) override; + + SIMPLE_RENDERABLE(); + +private: + QSharedPointer _procedural; +}; + + +#endif // hifi_RenderableShapeEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp deleted file mode 100644 index c3437b0e4a..0000000000 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// -// RenderableSphereEntityItem.cpp -// interface/src -// -// Created by Brad Hefta-Gaub on 8/6/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 "RenderableSphereEntityItem.h" - -#include - -#include - -#include -#include -#include - -#include -#include - -// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 -// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. -static const float SPHERE_ENTITY_SCALE = 0.5f; - - -EntityItemPointer RenderableSphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity{ new RenderableSphereEntityItem(entityID) }; - entity->setProperties(properties); - return entity; -} - -void RenderableSphereEntityItem::setUserData(const QString& value) { - if (value != getUserData()) { - SphereEntityItem::setUserData(value); - if (_procedural) { - _procedural->parse(value); - } - } -} - -void RenderableSphereEntityItem::render(RenderArgs* args) { - PerformanceTimer perfTimer("RenderableSphereEntityItem::render"); - Q_ASSERT(getType() == EntityTypes::Sphere); - Q_ASSERT(args->_batch); - - if (!_procedural) { - _procedural.reset(new Procedural(getUserData())); - _procedural->_vertexSource = simple_vert; - _procedural->_fragmentSource = simple_frag; - _procedural->_state->setCullMode(gpu::State::CULL_NONE); - _procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL); - _procedural->_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); - } - - gpu::Batch& batch = *args->_batch; - glm::vec4 sphereColor(toGlm(getXColor()), getLocalRenderAlpha()); - bool success; - Transform modelTransform = getTransformToCenter(success); - if (!success) { - return; - } - modelTransform.postScale(SPHERE_ENTITY_SCALE); - batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation - if (_procedural->ready()) { - _procedural->prepare(batch, getPosition(), getDimensions()); - auto color = _procedural->getColor(sphereColor); - batch._glColor4f(color.r, color.g, color.b, color.a); - DependencyManager::get()->renderSphere(batch); - } else { - DependencyManager::get()->renderSolidSphereInstance(batch, sphereColor); - } - static const auto triCount = DependencyManager::get()->getSphereTriangleCount(); - args->_details._trianglesRendered += (int)triCount; -} diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.h b/libraries/entities-renderer/src/RenderableSphereEntityItem.h deleted file mode 100644 index 5efe49854a..0000000000 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// RenderableSphereEntityItem.h -// interface/src/entities -// -// Created by Brad Hefta-Gaub on 8/6/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_RenderableSphereEntityItem_h -#define hifi_RenderableSphereEntityItem_h - -#include -#include - -#include "RenderableEntityItem.h" - -class RenderableSphereEntityItem : public SphereEntityItem { -public: - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - RenderableSphereEntityItem(const EntityItemID& entityItemID) : SphereEntityItem(entityItemID) { } - - virtual void render(RenderArgs* args) override; - virtual void setUserData(const QString& value) override; - - SIMPLE_RENDERABLE(); - -private: - QSharedPointer _procedural; -}; - - -#endif // hifi_RenderableSphereEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f0244d0e3f..8298dbcec5 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -226,10 +226,15 @@ void RenderableWebEntityItem::setSourceUrl(const QString& value) { } void RenderableWebEntityItem::setProxyWindow(QWindow* proxyWindow) { - _webSurface->setProxyWindow(proxyWindow); + if (_webSurface) { + _webSurface->setProxyWindow(proxyWindow); + } } QObject* RenderableWebEntityItem::getEventHandler() { + if (!_webSurface) { + return nullptr; + } return _webSurface->getEventHandler(); } diff --git a/libraries/entities-renderer/src/polyvox.slf b/libraries/entities-renderer/src/polyvox.slf index a77049b733..b7682913a7 100644 --- a/libraries/entities-renderer/src/polyvox.slf +++ b/libraries/entities-renderer/src/polyvox.slf @@ -41,5 +41,5 @@ void main(void) { vec3 yzDiffuseScaled = yzDiffuse.rgb * abs(worldNormal.x); vec4 diffuse = vec4(xyDiffuseScaled + xzDiffuseScaled + yzDiffuseScaled, 1.0); - packDeferredFragment(_normal, 1.0, vec3(diffuse), DEFAULT_ROUGHNESS, DEFAULT_METALLIC, DEFAULT_EMISSIVE, DEFAULT_OCCLUSION); + packDeferredFragment(_normal, 1.0, vec3(diffuse), DEFAULT_ROUGHNESS, DEFAULT_METALLIC, DEFAULT_EMISSIVE, DEFAULT_OCCLUSION, DEFAULT_SCATTERING); } diff --git a/libraries/entities/src/BoxEntityItem.cpp b/libraries/entities/src/BoxEntityItem.cpp deleted file mode 100644 index bf02d383ab..0000000000 --- a/libraries/entities/src/BoxEntityItem.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// -// BoxEntityItem.cpp -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/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 "BoxEntityItem.h" -#include "EntitiesLogging.h" -#include "EntityItemProperties.h" -#include "EntityTree.h" -#include "EntityTreeElement.h" - -EntityItemPointer BoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity { new BoxEntityItem(entityID) }; - entity->setProperties(properties); - return entity; -} - -BoxEntityItem::BoxEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { - _type = EntityTypes::Box; -} - -EntityItemProperties BoxEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { - EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - - properties._color = getXColor(); - properties._colorChanged = false; - - return properties; -} - -bool BoxEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = false; - somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - - SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); - - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "BoxEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties._lastEdited); - } - return somethingChanged; -} - -int BoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged) { - - int bytesRead = 0; - const unsigned char* dataAt = data; - - READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); - - return bytesRead; -} - - -// TODO: eventually only include properties changed since the params.lastViewFrustumSent time -EntityPropertyFlags BoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { - EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_COLOR; - return requestedProperties; -} - -void BoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - - APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); -} - -void BoxEntityItem::debugDump() const { - quint64 now = usecTimestampNow(); - qCDebug(entities) << " BOX EntityItem id:" << getEntityItemID() << "---------------------------------------------"; - qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; - qCDebug(entities) << " position:" << debugTreeVector(getPosition()); - qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); -} - diff --git a/libraries/entities/src/BoxEntityItem.h b/libraries/entities/src/BoxEntityItem.h deleted file mode 100644 index 6196346b9a..0000000000 --- a/libraries/entities/src/BoxEntityItem.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// BoxEntityItem.h -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/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_BoxEntityItem_h -#define hifi_BoxEntityItem_h - -#include "EntityItem.h" - -class BoxEntityItem : public EntityItem { -public: - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - BoxEntityItem(const EntityItemID& entityItemID); - - ALLOW_INSTANTIATION // This class can be instantiated - - // methods for getting/setting all properties of an entity - virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const; - virtual bool setProperties(const EntityItemProperties& properties); - - // TODO: eventually only include properties changed since the params.lastViewFrustumSent time - virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; - - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const; - - virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged); - - const rgbColor& getColor() const { return _color; } - xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } - - void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } - void setColor(const xColor& value) { - _color[RED_INDEX] = value.red; - _color[GREEN_INDEX] = value.green; - _color[BLUE_INDEX] = value.blue; - } - - virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; } - virtual bool shouldBePhysical() const { return !isDead(); } - - virtual void debugDump() const; - -protected: - rgbColor _color; -}; - -#endif // hifi_BoxEntityItem_h diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 1e38c32964..b96911c8c2 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include #include @@ -24,9 +25,7 @@ EntityEditPacketSender::EntityEditPacketSender() { } void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer message, SharedNodePointer sendingNode) { - if (_shouldProcessNack) { - processNackPacket(*message, sendingNode); - } + processNackPacket(*message, sendingNode); } void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, qint64 clockSkew) { @@ -35,18 +34,76 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte } } -void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityItemID modelID, - const EntityItemProperties& properties) { +void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, + EntityTreePointer entityTree, + EntityItemID entityItemID, + const EntityItemProperties& properties) { if (!_shouldSend) { return; // bail early } + if (properties.getOwningAvatarID() != _myAvatar->getID()) { + return; // don't send updates for someone else's avatarEntity + } + + assert(properties.getClientOnly()); + + // this is an avatar-based entity. update our avatar-data rather than sending to the entity-server + assert(_myAvatar); + + if (!entityTree) { + qDebug() << "EntityEditPacketSender::queueEditEntityMessage null entityTree."; + return; + } + EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID); + if (!entity) { + qDebug() << "EntityEditPacketSender::queueEditEntityMessage can't find entity."; + return; + } + + // the properties that get serialized into the avatar identity packet should be the entire set + // rather than just the ones being edited. + entity->setProperties(properties); + EntityItemProperties entityProperties = entity->getProperties(); + + QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties); + QVariant variantProperties = scriptProperties.toVariant(); + QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); + + // the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar + QJsonObject jsonObject = jsonProperties.object(); + if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) { + jsonObject["parentID"] = AVATAR_SELF_ID.toString(); + } + jsonProperties = QJsonDocument(jsonObject); + + QByteArray binaryProperties = jsonProperties.toBinaryData(); + _myAvatar->updateAvatarEntity(entityItemID, binaryProperties); + + entity->setLastBroadcast(usecTimestampNow()); + return; +} + + +void EntityEditPacketSender::queueEditEntityMessage(PacketType type, + EntityTreePointer entityTree, + EntityItemID entityItemID, + const EntityItemProperties& properties) { + if (!_shouldSend) { + return; // bail early + } + + if (properties.getClientOnly()) { + queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties); + return; + } + QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0); - if (EntityItemProperties::encodeEntityEditPacket(type, modelID, properties, bufferOut)) { + if (EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut)) { #ifdef WANT_DEBUG qCDebug(entities) << "calling queueOctreeEditMessage()..."; - qCDebug(entities) << " id:" << modelID; + qCDebug(entities) << " id:" << entityItemID; qCDebug(entities) << " properties:" << properties; #endif queueOctreeEditMessage(type, bufferOut); @@ -58,6 +115,10 @@ void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityI return; // bail early } + // in case this was a clientOnly entity: + assert(_myAvatar); + _myAvatar->clearAvatarEntity(entityItemID); + QByteArray bufferOut(NLPacket::maxPayloadSize(PacketType::EntityErase), 0); if (EntityItemProperties::encodeEraseEntityMessage(entityItemID, bufferOut)) { diff --git a/libraries/entities/src/EntityEditPacketSender.h b/libraries/entities/src/EntityEditPacketSender.h index 26e4dd83ff..1991142f3f 100644 --- a/libraries/entities/src/EntityEditPacketSender.h +++ b/libraries/entities/src/EntityEditPacketSender.h @@ -15,6 +15,7 @@ #include #include "EntityItem.h" +#include "AvatarData.h" /// Utility for processing, packing, queueing and sending of outbound edit voxel messages. class EntityEditPacketSender : public OctreeEditPacketSender { @@ -22,11 +23,21 @@ class EntityEditPacketSender : public OctreeEditPacketSender { public: EntityEditPacketSender(); + void setMyAvatar(AvatarData* myAvatar) { _myAvatar = myAvatar; } + AvatarData* getMyAvatar() { return _myAvatar; } + void clearAvatarEntity(QUuid entityID) { assert(_myAvatar); _myAvatar->clearAvatarEntity(entityID); } + + void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree, + EntityItemID entityItemID, const EntityItemProperties& properties); + + /// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines /// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in /// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known. /// NOTE: EntityItemProperties assumes that all distances are in meter units - void queueEditEntityMessage(PacketType type, EntityItemID modelID, const EntityItemProperties& properties); + void queueEditEntityMessage(PacketType type, EntityTreePointer entityTree, + EntityItemID entityItemID, const EntityItemProperties& properties); + void queueEraseEntityMessage(const EntityItemID& entityItemID); @@ -36,9 +47,9 @@ public: public slots: void processEntityEditNackPacket(QSharedPointer message, SharedNodePointer sendingNode); - void toggleNackPackets() { _shouldProcessNack = !_shouldProcessNack; } private: - bool _shouldProcessNack = true; + AvatarData* _myAvatar { nullptr }; + QScriptEngine _scriptEngine; }; #endif // hifi_EntityEditPacketSender_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index bd4292f75d..f0a4d40860 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -137,6 +137,9 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_PARENT_JOINT_INDEX; requestedProperties += PROP_QUERY_AA_CUBE; + requestedProperties += PROP_CLIENT_ONLY; + requestedProperties += PROP_OWNING_AVATAR_ID; + return requestedProperties; } @@ -684,15 +687,80 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } } { // When we own the simulation we don't accept updates to the entity's transform/velocities - // but since we're using macros below we have to temporarily modify overwriteLocalData. - bool oldOverwrite = overwriteLocalData; - overwriteLocalData = overwriteLocalData && !weOwnSimulation; - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionFromNetwork); - READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotationFromNetwork); - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocityFromNetwork); - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityFromNetwork); - READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); - overwriteLocalData = oldOverwrite; + // we also want to ignore any duplicate packets that have the same "recently updated" values + // as a packet we've already recieved. This is because we want multiple edits of the same + // information to be idempotent, but if we applied new physics properties we'd resimulation + // with small differences in results. + + // Because the regular streaming property "setters" only have access to the new value, we've + // made these lambdas that can access other details about the previous updates to suppress + // any duplicates. + + // Note: duplicate packets are expected and not wrong. They may be sent for any number of + // reasons and the contract is that the client handles them in an idempotent manner. + auto lastEdited = lastEditedFromBufferAdjusted; + auto customUpdatePositionFromNetwork = [this, lastEdited, overwriteLocalData, weOwnSimulation](glm::vec3 value){ + bool simulationChanged = lastEdited > _lastUpdatedPositionTimestamp; + bool valueChanged = value != _lastUpdatedPositionValue; + bool shouldUpdate = overwriteLocalData && !weOwnSimulation && simulationChanged && valueChanged; + if (shouldUpdate) { + updatePositionFromNetwork(value); + _lastUpdatedPositionTimestamp = lastEdited; + _lastUpdatedPositionValue = value; + } + }; + + auto customUpdateRotationFromNetwork = [this, lastEdited, overwriteLocalData, weOwnSimulation](glm::quat value){ + bool simulationChanged = lastEdited > _lastUpdatedRotationTimestamp; + bool valueChanged = value != _lastUpdatedRotationValue; + bool shouldUpdate = overwriteLocalData && !weOwnSimulation && simulationChanged && valueChanged; + if (shouldUpdate) { + updateRotationFromNetwork(value); + _lastUpdatedRotationTimestamp = lastEdited; + _lastUpdatedRotationValue = value; + } + }; + + auto customUpdateVelocityFromNetwork = [this, lastEdited, overwriteLocalData, weOwnSimulation](glm::vec3 value){ + bool simulationChanged = lastEdited > _lastUpdatedVelocityTimestamp; + bool valueChanged = value != _lastUpdatedVelocityValue; + bool shouldUpdate = overwriteLocalData && !weOwnSimulation && simulationChanged && valueChanged; + if (shouldUpdate) { + updateVelocityFromNetwork(value); + _lastUpdatedVelocityTimestamp = lastEdited; + _lastUpdatedVelocityValue = value; + } + }; + + auto customUpdateAngularVelocityFromNetwork = [this, lastEdited, overwriteLocalData, weOwnSimulation](glm::vec3 value){ + bool simulationChanged = lastEdited > _lastUpdatedAngularVelocityTimestamp; + bool valueChanged = value != _lastUpdatedAngularVelocityValue; + bool shouldUpdate = overwriteLocalData && !weOwnSimulation && simulationChanged && valueChanged; + if (shouldUpdate) { + updateAngularVelocityFromNetwork(value); + _lastUpdatedAngularVelocityTimestamp = lastEdited; + _lastUpdatedAngularVelocityValue = value; + } + }; + + auto customSetAcceleration = [this, lastEdited, overwriteLocalData, weOwnSimulation](glm::vec3 value){ + bool simulationChanged = lastEdited > _lastUpdatedAccelerationTimestamp; + bool valueChanged = value != _lastUpdatedAccelerationValue; + bool shouldUpdate = overwriteLocalData && !weOwnSimulation && simulationChanged && valueChanged; + if (shouldUpdate) { + setAcceleration(value); + _lastUpdatedAccelerationTimestamp = lastEdited; + _lastUpdatedAccelerationValue = value; + } + }; + + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, customUpdatePositionFromNetwork); + READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, customUpdateRotationFromNetwork); + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, customUpdateVelocityFromNetwork); + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, customUpdateAngularVelocityFromNetwork); + READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, customSetAcceleration); + + } READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); @@ -919,13 +987,11 @@ void EntityItem::simulate(const quint64& now) { qCDebug(entities) << " ********** EntityItem::simulate() .... SETTING _lastSimulated=" << _lastSimulated; #endif - if (!hasActions()) { - if (!stepKinematicMotion(timeElapsed)) { - // this entity is no longer moving - // flag it to transition from KINEMATIC to STATIC - _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; - setAcceleration(Vectors::ZERO); - } + if (!stepKinematicMotion(timeElapsed)) { + // this entity is no longer moving + // flag it to transition from KINEMATIC to STATIC + _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; + setAcceleration(Vectors::ZERO); } _lastSimulated = now; } @@ -1093,6 +1159,8 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper properties._id = getID(); properties._idSet = true; properties._created = _created; + properties.setClientOnly(_clientOnly); + properties.setOwningAvatarID(_owningAvatarID); properties._type = getType(); @@ -1133,6 +1201,9 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(localPosition, getLocalPosition); COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRotation, getLocalOrientation); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(clientOnly, getClientOnly); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarID); + properties._defaultSettings = false; return properties; @@ -1222,6 +1293,9 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex); SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(clientOnly, setClientOnly); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(owningAvatarID, setOwningAvatarID); + AACube saveQueryAACube = _queryAACube; checkAndAdjustQueryAACube(); if (saveQueryAACube != _queryAACube) { @@ -1528,14 +1602,20 @@ void EntityItem::updateMass(float mass) { void EntityItem::updateVelocity(const glm::vec3& value) { glm::vec3 velocity = getLocalVelocity(); if (velocity != value) { - const float MIN_LINEAR_SPEED = 0.001f; - if (glm::length(value) < MIN_LINEAR_SPEED) { - velocity = ENTITY_ITEM_ZERO_VEC3; + if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { + if (velocity != Vectors::ZERO) { + setLocalVelocity(Vectors::ZERO); + } } else { - velocity = value; + const float MIN_LINEAR_SPEED = 0.001f; + if (glm::length(value) < MIN_LINEAR_SPEED) { + velocity = ENTITY_ITEM_ZERO_VEC3; + } else { + velocity = value; + } + setLocalVelocity(velocity); + _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; } - setLocalVelocity(velocity); - _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; } } @@ -1556,22 +1636,30 @@ void EntityItem::updateDamping(float value) { void EntityItem::updateGravity(const glm::vec3& value) { if (_gravity != value) { - _gravity = value; - _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; + if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { + _gravity = Vectors::ZERO; + } else { + _gravity = value; + _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; + } } } void EntityItem::updateAngularVelocity(const glm::vec3& value) { glm::vec3 angularVelocity = getLocalAngularVelocity(); if (angularVelocity != value) { - const float MIN_ANGULAR_SPEED = 0.0002f; - if (glm::length(value) < MIN_ANGULAR_SPEED) { - angularVelocity = ENTITY_ITEM_ZERO_VEC3; + if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { + setLocalAngularVelocity(Vectors::ZERO); } else { - angularVelocity = value; + const float MIN_ANGULAR_SPEED = 0.0002f; + if (glm::length(value) < MIN_ANGULAR_SPEED) { + angularVelocity = ENTITY_ITEM_ZERO_VEC3; + } else { + angularVelocity = value; + } + setLocalAngularVelocity(angularVelocity); + _dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY; } - setLocalAngularVelocity(angularVelocity); - _dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY; } } @@ -1605,9 +1693,17 @@ void EntityItem::updateCollisionMask(uint8_t value) { } void EntityItem::updateDynamic(bool value) { - if (_dynamic != value) { - _dynamic = value; - _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; + if (getDynamic() != value) { + // dynamic and STATIC_MESH are incompatible so we check for that case + if (value && getShapeType() == SHAPE_TYPE_STATIC_MESH) { + if (_dynamic) { + _dynamic = false; + _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; + } + } else { + _dynamic = value; + _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; + } } } @@ -1657,7 +1753,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask group = BULLET_COLLISION_GROUP_COLLISIONLESS; mask = 0; } else { - if (_dynamic) { + if (getDynamic()) { group = BULLET_COLLISION_GROUP_DYNAMIC; } else if (isMovingRelativeToParent() || hasActions()) { group = BULLET_COLLISION_GROUP_KINEMATIC; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4d3d93e40f..9fa13690f1 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -283,7 +283,7 @@ public: void computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask) const; - bool getDynamic() const { return _dynamic; } + bool getDynamic() const { return SHAPE_TYPE_STATIC_MESH == getShapeType() ? false : _dynamic; } void setDynamic(bool value) { _dynamic = value; } virtual bool shouldBePhysical() const { return false; } @@ -348,7 +348,7 @@ public: void updateDynamic(bool value); void updateLifetime(float value); void updateCreated(uint64_t value); - virtual void updateShapeType(ShapeType type) { /* do nothing */ } + virtual void setShapeType(ShapeType type) { /* do nothing */ } uint32_t getDirtyFlags() const { return _dirtyFlags; } void clearDirtyFlags(uint32_t mask = 0xffffffff) { _dirtyFlags &= ~mask; } @@ -372,6 +372,7 @@ public: glm::vec3 entityToWorld(const glm::vec3& point) const; quint64 getLastEditedFromRemote() const { return _lastEditedFromRemote; } + void updateLastEditedFromRemote() { _lastEditedFromRemote = usecTimestampNow(); } void getAllTerseUpdateProperties(EntityItemProperties& properties) const; @@ -422,12 +423,19 @@ public: /// entity to definitively state if the preload signal should be sent. /// /// We only want to preload if: - /// there is some script, and either the script value or the scriptTimestamp + /// there is some script, and either the script value or the scriptTimestamp /// value have changed since our last preload - bool shouldPreloadScript() const { return !_script.isEmpty() && + bool shouldPreloadScript() const { return !_script.isEmpty() && ((_loadedScript != _script) || (_loadedScriptTimestamp != _scriptTimestamp)); } void scriptHasPreloaded() { _loadedScript = _script; _loadedScriptTimestamp = _scriptTimestamp; } + bool getClientOnly() const { return _clientOnly; } + void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; } + // if this entity is client-only, which avatar is it associated with? + QUuid getOwningAvatarID() const { return _owningAvatarID; } + void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; } + + protected: void setSimulated(bool simulated) { _simulated = simulated; } @@ -539,6 +547,25 @@ protected: mutable QHash _previouslyDeletedActions; QUuid _sourceUUID; /// the server node UUID we came from + + bool _clientOnly { false }; + QUuid _owningAvatarID; + + // physics related changes from the network to suppress any duplicates and make + // sure redundant applications are idempotent + glm::vec3 _lastUpdatedPositionValue; + glm::quat _lastUpdatedRotationValue; + glm::vec3 _lastUpdatedVelocityValue; + glm::vec3 _lastUpdatedAngularVelocityValue; + glm::vec3 _lastUpdatedAccelerationValue; + + quint64 _lastUpdatedPositionTimestamp { 0 }; + quint64 _lastUpdatedRotationTimestamp { 0 }; + quint64 _lastUpdatedVelocityTimestamp { 0 }; + quint64 _lastUpdatedAngularVelocityTimestamp { 0 }; + quint64 _lastUpdatedAccelerationTimestamp { 0 }; + + }; #endif // hifi_EntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 7893fe4a37..dcd7e25bc1 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -88,8 +88,23 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) { _lastEdited = usecTime > _created ? usecTime : _created; } -const char* shapeTypeNames[] = {"none", "box", "sphere", "ellipsoid", "plane", "compound", "capsule-x", - "capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"}; +const char* shapeTypeNames[] = { + "none", + "box", + "sphere", + "capsule-x", + "capsule-y", + "capsule-z", + "cylinder-x", + "cylinder-y", + "cylinder-z", + "hull", + "plane", + "compound", + "simple-hull", + "simple-compound", + "static-mesh" +}; QHash stringToShapeTypeLookup; @@ -101,15 +116,18 @@ void buildStringToShapeTypeLookup() { addShapeType(SHAPE_TYPE_NONE); addShapeType(SHAPE_TYPE_BOX); addShapeType(SHAPE_TYPE_SPHERE); - addShapeType(SHAPE_TYPE_ELLIPSOID); - addShapeType(SHAPE_TYPE_PLANE); - addShapeType(SHAPE_TYPE_COMPOUND); addShapeType(SHAPE_TYPE_CAPSULE_X); addShapeType(SHAPE_TYPE_CAPSULE_Y); addShapeType(SHAPE_TYPE_CAPSULE_Z); addShapeType(SHAPE_TYPE_CYLINDER_X); addShapeType(SHAPE_TYPE_CYLINDER_Y); addShapeType(SHAPE_TYPE_CYLINDER_Z); + addShapeType(SHAPE_TYPE_HULL); + addShapeType(SHAPE_TYPE_PLANE); + addShapeType(SHAPE_TYPE_COMPOUND); + addShapeType(SHAPE_TYPE_SIMPLE_HULL); + addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND); + addShapeType(SHAPE_TYPE_STATIC_MESH); } QString getCollisionGroupAsString(uint8_t group) { @@ -308,6 +326,14 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_LOCAL_POSITION, localPosition); CHECK_PROPERTY_CHANGE(PROP_LOCAL_ROTATION, localRotation); + CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed); + CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed); + + CHECK_PROPERTY_CHANGE(PROP_CLIENT_ONLY, clientOnly); + CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID); + + CHECK_PROPERTY_CHANGE(PROP_SHAPE, shape); + changedProperties += _animation.getChangedProperties(); changedProperties += _keyLight.getChangedProperties(); changedProperties += _skybox.getChangedProperties(); @@ -429,6 +455,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Sphere) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, QString("Sphere")); } + if (_type == EntityTypes::Box || _type == EntityTypes::Sphere || _type == EntityTypes::Shape) { + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHAPE, shape); + } // FIXME - it seems like ParticleEffect should also support this if (_type == EntityTypes::Model || _type == EntityTypes::Zone) { @@ -465,6 +494,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool _stage.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GHOSTING_ALLOWED, ghostingAllowed); } // Web only @@ -539,6 +571,12 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); + + properties.setProperty("clientOnly", convertScriptValue(engine, getClientOnly())); + properties.setProperty("owningAvatarID", convertScriptValue(engine, getOwningAvatarID())); + // FIXME - I don't think these properties are supported any more //COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); @@ -674,6 +712,13 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotations, qVectorQuat, setJointRotations); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslations, qVectorVec3, setJointTranslations); + COPY_PROPERTY_FROM_QSCRIPTVALUE(shape, QString, setShape); + + COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed); + COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed); + + COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly); + COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID); _lastEdited = usecTimestampNow(); } @@ -825,6 +870,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector); ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector); + ADD_PROPERTY_TO_MAP(PROP_SHAPE, Shape, shape, QString); + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FPS, Animation, animation, FPS, fps); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FRAME_INDEX, Animation, animation, CurrentFrame, currentFrame); @@ -845,6 +892,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_HOUR, Stage, stage, Hour, hour); ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_AUTOMATIC_HOURDAY, Stage, stage, AutomaticHourDay, automaticHourDay); + ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool); + ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool); + // FIXME - these are not yet handled //ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64); @@ -1085,6 +1135,9 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem _staticSkybox.setProperties(properties); _staticSkybox.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + + APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, properties.getFlyingAllowed()); + APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, properties.getGhostingAllowed()); } if (properties.getType() == EntityTypes::PolyVox) { @@ -1114,7 +1167,13 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, properties.getStrokeWidths()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures()); } - + // NOTE: Spheres and Boxes are just special cases of Shape, and they need to include their PROP_SHAPE + // when encoding/decoding edits because otherwise they can't polymorph to other shape types + if (properties.getType() == EntityTypes::Shape || + properties.getType() == EntityTypes::Box || + properties.getType() == EntityTypes::Sphere) { + APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); + } APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID()); APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); @@ -1179,7 +1238,6 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem } else { packetData->discardSubTree(); } - return success; } @@ -1369,6 +1427,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); properties.getSkybox().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); + + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); } if (properties.getType() == EntityTypes::PolyVox) { @@ -1400,6 +1461,14 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXTURES, QString, setTextures); } + // NOTE: Spheres and Boxes are just special cases of Shape, and they need to include their PROP_SHAPE + // when encoding/decoding edits because otherwise they can't polymorph to other shape types + if (properties.getType() == EntityTypes::Shape || + properties.getType() == EntityTypes::Box || + properties.getType() == EntityTypes::Sphere) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); + } + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MARKETPLACE_ID, QString, setMarketplaceID); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); @@ -1559,6 +1628,12 @@ void EntityItemProperties::markAllChanged() { _jointTranslationsChanged = true; _queryAACubeChanged = true; + + _flyingAllowedChanged = true; + _ghostingAllowedChanged = true; + + _clientOnlyChanged = true; + _owningAvatarIDChanged = true; } // The minimum bounding box for the entity. @@ -1880,6 +1955,24 @@ QList EntityItemProperties::listChangedProperties() { out += "queryAACube"; } + if (clientOnlyChanged()) { + out += "clientOnly"; + } + if (owningAvatarIDChanged()) { + out += "owningAvatarID"; + } + + if (flyingAllowedChanged()) { + out += "flyingAllowed"; + } + if (ghostingAllowedChanged()) { + out += "ghostingAllowed"; + } + + if (shapeChanged()) { + out += "shape"; + } + getAnimation().listChangedProperties(out); getKeyLight().listChangedProperties(out); getSkybox().listChangedProperties(out); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 49981913b5..6018ba793f 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -50,7 +50,7 @@ const quint64 UNKNOWN_CREATED_TIME = 0; /// A collection of properties of an entity item used in the scripting API. Translates between the actual properties of an /// entity and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete /// set of entity item properties via JavaScript hashes/QScriptValues -/// all units for position, dimensions, etc are in meter units +/// all units for SI units (meter, second, radian, etc) class EntityItemProperties { friend class EntityItem; // TODO: consider removing this friend relationship and use public methods friend class ModelEntityItem; // TODO: consider removing this friend relationship and use public methods @@ -64,6 +64,7 @@ class EntityItemProperties { friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods + friend class ShapeEntityItem; // TODO: consider removing this friend relationship and use public methods public: EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()); virtual ~EntityItemProperties() = default; @@ -195,6 +196,7 @@ public: DEFINE_PROPERTY_REF(PROP_PARENT_ID, ParentID, parentID, QUuid, UNKNOWN_ENTITY_ID); DEFINE_PROPERTY_REF(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, quint16, -1); DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube()); + DEFINE_PROPERTY_REF(PROP_SHAPE, Shape, shape, QString, "Sphere"); // 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); @@ -205,6 +207,12 @@ public: DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector, QVector()); + DEFINE_PROPERTY(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool, ZoneEntityItem::DEFAULT_FLYING_ALLOWED); + DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED); + + DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false); + DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID); + static QString getBackgroundModeString(BackgroundMode mode); @@ -416,6 +424,12 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointTranslationsSet, jointTranslationsSet, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointTranslations, jointTranslations, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, FlyingAllowed, flyingAllowed, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, ""); + + DEBUG_PROPERTY_IF_CHANGED(debug, properties, ClientOnly, clientOnly, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, ""); + properties.getAnimation().debugDump(); properties.getSkybox().debugDump(); properties.getStage().debugDump(); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 90a7c1e2f7..36bb37c8f3 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -169,6 +169,14 @@ enum EntityPropertyList { PROP_FALLOFF_RADIUS, // for Light entity + PROP_FLYING_ALLOWED, // can avatars in a zone fly? + PROP_GHOSTING_ALLOWED, // can avatars in a zone turn off physics? + + PROP_CLIENT_ONLY, // doesn't go over wire + PROP_OWNING_AVATAR_ID, // doesn't go over wire + + PROP_SHAPE, + //////////////////////////////////////////////////////////////////////////////////////////////////// // 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 01f444491d..856e526b4c 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -32,11 +32,12 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership auto nodeList = DependencyManager::get(); connect(nodeList.data(), &NodeList::isAllowedEditorChanged, this, &EntityScriptingInterface::canAdjustLocksChanged); connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptingInterface::canRezChanged); + connect(nodeList.data(), &NodeList::canRezTmpChanged, this, &EntityScriptingInterface::canRezTmpChanged); } void EntityScriptingInterface::queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties) { - getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties); + getEntityPacketSender()->queueEditEntityMessage(packetType, _entityTree, entityID, properties); } bool EntityScriptingInterface::canAdjustLocks() { @@ -49,6 +50,11 @@ bool EntityScriptingInterface::canRez() { return nodeList->getThisNodeCanRez(); } +bool EntityScriptingInterface::canRezTmp() { + auto nodeList = DependencyManager::get(); + return nodeList->getThisNodeCanRezTmp(); +} + void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { if (_entityTree) { disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); @@ -123,10 +129,17 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti } -QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { +QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) { EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); + if (clientOnly) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + propertiesWithSimID.setClientOnly(clientOnly); + propertiesWithSimID.setOwningAvatarID(myNodeID); + } + auto dimensions = propertiesWithSimID.getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; auto density = propertiesWithSimID.getDensity(); @@ -166,6 +179,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties } entity->setLastBroadcast(usecTimestampNow()); + propertiesWithSimID.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; @@ -177,9 +191,11 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties if (success) { emit debitEnergySource(cost); queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID); - } - return id; + return id; + } else { + return QUuid(); + } } QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const glm::vec3& position) { @@ -272,13 +288,21 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& bool updatedEntity = false; _entityTree->withWriteLock([&] { + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + if (!entity) { + return; + } + + auto nodeList = DependencyManager::get(); + if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) { + // don't edit other avatar's avatarEntities + return; + } + if (scriptSideProperties.parentRelatedPropertyChanged()) { // 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; - } + //existing entity, retrieve old velocity for check down below oldVelocity = entity->getVelocity().length(); @@ -296,6 +320,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } } properties = convertLocationFromScriptSemantics(properties); + properties.setClientOnly(entity->getClientOnly()); + properties.setOwningAvatarID(entity->getOwningAvatarID()); float cost = calculateCost(density * volume, oldVelocity, newVelocity); cost *= costMultiplier; @@ -353,6 +379,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& properties.setQueryAACube(entity->getQueryAACube()); } entity->setLastBroadcast(usecTimestampNow()); + properties.setLastEdited(entity->getLastEdited()); // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. @@ -384,6 +411,14 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + if (entity->getClientOnly() && entity->getOwningAvatarID() != myNodeID) { + // don't delete other avatar's avatarEntities + shouldDelete = false; + return; + } + auto dimensions = entity->getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; auto density = entity->getDensity(); @@ -771,6 +806,11 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, return false; } + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + + EntityItemProperties properties; + EntityItemPointer entity; bool doTransmit = false; _entityTree->withWriteLock([&] { @@ -786,15 +826,20 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, return; } + if (entity->getClientOnly() && entity->getOwningAvatarID() != myNodeID) { + return; + } + doTransmit = actor(simulation, entity); if (doTransmit) { + properties.setClientOnly(entity->getClientOnly()); + properties.setOwningAvatarID(entity->getOwningAvatarID()); _entityTree->entityChanged(entity); } }); // transmit the change if (doTransmit) { - EntityItemProperties properties; _entityTree->withReadLock([&] { properties = entity->getProperties(); }); @@ -1086,6 +1131,27 @@ QStringList EntityScriptingInterface::getJointNames(const QUuid& entityID) { return result; } +QVector EntityScriptingInterface::getChildrenIDs(const QUuid& parentID) { + QVector result; + if (!_entityTree) { + return result; + } + + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(parentID); + if (!entity) { + qDebug() << "EntityScriptingInterface::getChildrenIDs - no entity with ID" << parentID; + return result; + } + + _entityTree->withReadLock([&] { + entity->forEachChild([&](SpatiallyNestablePointer child) { + result.push_back(child->getID()); + }); + }); + + return result; +} + QVector EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex) { QVector result; if (!_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 5f80b7abb2..e9024eb721 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -80,9 +80,10 @@ public slots: // returns true if the DomainServer will allow this Node/Avatar to rez new entities Q_INVOKABLE bool canRez(); + Q_INVOKABLE bool canRezTmp(); /// adds a model with the specific properties - Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties); + Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool clientOnly = false); /// temporary method until addEntity can be used from QJSEngine Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const glm::vec3& position); @@ -171,6 +172,7 @@ public slots: Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name); Q_INVOKABLE QStringList getJointNames(const QUuid& entityID); + Q_INVOKABLE QVector getChildrenIDs(const QUuid& parentID); Q_INVOKABLE QVector getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex); signals: @@ -178,6 +180,7 @@ signals: void canAdjustLocksChanged(bool canAdjustLocks); void canRezChanged(bool canRez); + void canRezTmpChanged(bool canRez); void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 893ae83cc5..a29ea8e2c8 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -261,7 +261,14 @@ void EntitySimulation::moveSimpleKinematics(const quint64& now) { SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin(); while (itemItr != _simpleKinematicEntities.end()) { EntityItemPointer entity = *itemItr; - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { + + // The entity-server doesn't know where avatars are, so don't attempt to do simple extrapolation for + // children of avatars. See related code in EntityMotionState::remoteSimulationOutOfSync. + bool ancestryIsKnown; + entity->getMaximumAACube(ancestryIsKnown); + bool hasAvatarAncestor = entity->hasAncestorOfType(NestableType::Avatar); + + if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo() && ancestryIsKnown && !hasAvatarAncestor) { entity->simulate(now); _entitiesToSort.insert(entity); ++itemItr; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 30c37d46b1..ef0401ceaf 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -26,6 +26,8 @@ #include "LogHandler.h" static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; +const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour + EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage), @@ -310,17 +312,21 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer result = NULL; + EntityItemProperties props = properties; - if (getIsClient()) { - // if our Node isn't allowed to create entities in this domain, don't try. - auto nodeList = DependencyManager::get(); - if (nodeList && !nodeList->getThisNodeCanRez()) { - return NULL; - } + auto nodeList = DependencyManager::get(); + if (!nodeList) { + qDebug() << "EntityTree::addEntity -- can't get NodeList"; + return nullptr; + } + + if (!properties.getClientOnly() && getIsClient() && + !nodeList->getThisNodeCanRez() && !nodeList->getThisNodeCanRezTmp()) { + return nullptr; } bool recordCreationTime = false; - if (properties.getCreated() == UNKNOWN_CREATED_TIME) { + if (props.getCreated() == UNKNOWN_CREATED_TIME) { // the entity's creation time was not specified in properties, which means this is a NEW entity // and we must record its creation time recordCreationTime = true; @@ -335,8 +341,8 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti } // construct the instance of the entity - EntityTypes::EntityType type = properties.getType(); - result = EntityTypes::constructEntityItem(type, entityID, properties); + EntityTypes::EntityType type = props.getType(); + result = EntityTypes::constructEntityItem(type, entityID, props); if (result) { if (recordCreationTime) { @@ -853,6 +859,13 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList= 0) { + float value = properties.getLifetime(); + changedProperties[index] = QString("lifetime:") + QString::number((int)value); + } + } } int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, @@ -885,11 +898,25 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c EntityItemID entityItemID; EntityItemProperties properties; startDecode = usecTimestampNow(); - + bool validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes, entityItemID, properties); endDecode = usecTimestampNow(); + const quint64 LAST_EDITED_SERVERSIDE_BUMP = 1; // usec + if ((message.getType() == PacketType::EntityAdd || + (message.getType() == PacketType::EntityEdit && properties.lifetimeChanged())) && + !senderNode->getCanRez() && senderNode->getCanRezTmp()) { + // this node is only allowed to rez temporary entities. if need be, cap the lifetime. + if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME || + properties.getLifetime() > _maxTmpEntityLifetime) { + properties.setLifetime(_maxTmpEntityLifetime); + // also bump up the lastEdited time of the properties so that the interface that created this edit + // will accept our adjustment to lifetime back into its own entity-tree. + properties.setLastEdited(properties.getLastEdited() + LAST_EDITED_SERVERSIDE_BUMP); + } + } + // If we got a valid edit packet, then it could be a new entity or it could be an update to // an existing entity... handle appropriately if (validEditPacket) { @@ -918,7 +945,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c endUpdate = usecTimestampNow(); _totalUpdates++; } else if (message.getType() == PacketType::EntityAdd) { - if (senderNode->getCanRez()) { + if (senderNode->getCanRez() || senderNode->getCanRezTmp()) { // this is a new entity... assign a new entityID properties.setCreated(properties.getLastEdited()); startCreate = usecTimestampNow(); @@ -1282,6 +1309,7 @@ class ContentsDimensionOperator : public RecurseOctreeOperator { public: virtual bool preRecursion(OctreeElementPointer element); virtual bool postRecursion(OctreeElementPointer element) { return true; } + glm::vec3 getDimensions() const { return _contentExtents.size(); } float getLargestDimension() const { return _contentExtents.largestDimension(); } private: Extents _contentExtents; @@ -1293,6 +1321,12 @@ bool ContentsDimensionOperator::preRecursion(OctreeElementPointer element) { return true; } +glm::vec3 EntityTree::getContentsDimensions() { + ContentsDimensionOperator theOperator; + recurseTreeWithOperator(&theOperator); + return theOperator.getDimensions(); +} + float EntityTree::getContentsLargestDimension() { ContentsDimensionOperator theOperator; recurseTreeWithOperator(&theOperator); @@ -1376,10 +1410,17 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra item->globalizeProperties(properties, "Cannot find %3 parent of %2 %1", args->root); } } + + // set creation time to "now" for imported entities + properties.setCreated(usecTimestampNow()); + properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + EntityTreePointer tree = entityTreeElement->getTree(); + // queue the packet to send to the server - args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties); + args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, tree, newID, properties); // also update the local tree instantly (note: this is not our tree, but an alternate tree) if (args->otherTree) { @@ -1414,6 +1455,12 @@ bool EntityTree::readFromMap(QVariantMap& map) { QVariantList entitiesQList = map["Entities"].toList(); QScriptEngine scriptEngine; + if (entitiesQList.length() == 0) { + // Empty map or invalidly formed file. + return false; + } + + bool success = true; foreach (QVariant entityVariant, entitiesQList) { // QVariantMap --> QScriptValue --> EntityItemProperties --> Entity QVariantMap entityMap = entityVariant.toMap(); @@ -1431,9 +1478,10 @@ bool EntityTree::readFromMap(QVariantMap& map) { EntityItemPointer entity = addEntity(entityItemID, properties); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); + success = false; } } - return true; + return success; } void EntityTree::resetClientEditStats() { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 03b8a9b55a..15daf3bf3c 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -62,6 +62,9 @@ public: void createRootElement(); + + void setEntityMaxTmpLifetime(float maxTmpEntityLifetime) { _maxTmpEntityLifetime = maxTmpEntityLifetime; } + /// Implements our type specific root element factory virtual OctreeElementPointer createNewElement(unsigned char* octalCode = NULL) override; @@ -207,6 +210,7 @@ public: bool skipThoseWithBadParents) override; virtual bool readFromMap(QVariantMap& entityDescription) override; + glm::vec3 getContentsDimensions(); float getContentsLargestDimension(); virtual void resetEditStats() override { @@ -251,6 +255,8 @@ public: void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID); + static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; + public slots: void callLoader(EntityItemID entityID); @@ -330,6 +336,8 @@ protected: // we maintain a list of avatarIDs to notice when an entity is a child of one. QSet _avatarIDs; // IDs of avatars connected to entity server QHash> _childrenOfAvatars; // which entities are children of which avatars + + float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME }; }; #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 37a0f36d2f..0523933ee6 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -55,7 +55,7 @@ void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) cons if (extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData - = static_cast(extraEncodeData->value(this)); + = static_cast((*extraEncodeData)[this]); qCDebug(entities) << " encode data:" << entityTreeElementExtraEncodeData; } else { qCDebug(entities) << " encode data: MISSING!!"; @@ -97,7 +97,7 @@ bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamPa if (extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData - = static_cast(extraEncodeData->value(this)); + = static_cast((*extraEncodeData)[this]); bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex]; @@ -126,7 +126,7 @@ bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const if (extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData - = static_cast(extraEncodeData->value(this)); + = static_cast((*extraEncodeData)[this]); // If we know that ALL subtrees below us have already been recursed, then we don't // need to recurse this child. @@ -140,7 +140,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes if (extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData - = static_cast(extraEncodeData->value(this)); + = static_cast((*extraEncodeData)[this]); if (childAppendState == OctreeElement::COMPLETED) { entityTreeElementExtraEncodeData->childCompleted[childIndex] = true; @@ -165,7 +165,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con assert(extraEncodeData->contains(this)); EntityTreeElementExtraEncodeData* thisExtraEncodeData - = static_cast(extraEncodeData->value(this)); + = static_cast((*extraEncodeData)[this]); // Note: this will be called when OUR element has finished running through encodeTreeBitstreamRecursion() // which means, it's possible that our parent element hasn't finished encoding OUR data... so @@ -189,7 +189,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con // encoud our parent... this might happen. if (extraEncodeData->contains(childElement.get())) { EntityTreeElementExtraEncodeData* childExtraEncodeData - = static_cast(extraEncodeData->value(childElement.get())); + = static_cast((*extraEncodeData)[childElement.get()]); if (wantDebug) { qCDebug(entities) << "checking child: " << childElement->_cube; @@ -241,7 +241,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData bool hadElementExtraData = false; if (extraEncodeData && extraEncodeData->contains(this)) { entityTreeElementExtraEncodeData = - static_cast(extraEncodeData->value(this)); + static_cast((*extraEncodeData)[this]); hadElementExtraData = true; } else { // if there wasn't one already, then create one @@ -268,7 +268,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData //assert(extraEncodeData); //assert(extraEncodeData->contains(this)); - //entityTreeElementExtraEncodeData = static_cast(extraEncodeData->value(this)); + //entityTreeElementExtraEncodeData = static_cast((*extraEncodeData)[this]); LevelDetails elementLevel = packetData->startLevel(); diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 52c2242629..4ba4ad5676 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -17,18 +17,18 @@ #include "EntityItem.h" #include "EntityItemProperties.h" #include "EntityTypes.h" +#include "EntitiesLogging.h" -#include "BoxEntityItem.h" #include "LightEntityItem.h" #include "ModelEntityItem.h" #include "ParticleEffectEntityItem.h" -#include "SphereEntityItem.h" #include "TextEntityItem.h" #include "WebEntityItem.h" #include "ZoneEntityItem.h" #include "LineEntityItem.h" #include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" +#include "ShapeEntityItem.h" QMap EntityTypes::_typeToNameMap; QMap EntityTypes::_nameToTypeMap; @@ -39,16 +39,17 @@ const QString ENTITY_TYPE_NAME_UNKNOWN = "Unknown"; // Register Entity the default implementations of entity types here... REGISTER_ENTITY_TYPE(Model) -REGISTER_ENTITY_TYPE(Box) REGISTER_ENTITY_TYPE(Web) -REGISTER_ENTITY_TYPE(Sphere) REGISTER_ENTITY_TYPE(Light) REGISTER_ENTITY_TYPE(Text) REGISTER_ENTITY_TYPE(ParticleEffect) REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Line) REGISTER_ENTITY_TYPE(PolyVox) -REGISTER_ENTITY_TYPE(PolyLine); +REGISTER_ENTITY_TYPE(PolyLine) +REGISTER_ENTITY_TYPE(Shape) +REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory) +REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { QMap::iterator matchedTypeName = _typeToNameMap.find(entityType); @@ -63,6 +64,9 @@ EntityTypes::EntityType EntityTypes::getEntityTypeFromName(const QString& name) if (matchedTypeName != _nameToTypeMap.end()) { return matchedTypeName.value(); } + if (name.size() > 0 && name[0].isLower()) { + qCDebug(entities) << "Entity types must start with an uppercase letter. Please change the type" << name; + } return Unknown; } diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 3536327d18..016cda6cf7 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -48,7 +48,8 @@ public: Line, PolyVox, PolyLine, - LAST = PolyLine + Shape, + LAST = Shape } EntityType; static const QString& getEntityTypeName(EntityType entityType); @@ -69,9 +70,18 @@ private: /// named NameEntityItem and must of a static method called factory that takes an EnityItemID, and EntityItemProperties and return a newly /// constructed (heap allocated) instance of your type. e.g. The following prototype: // static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); -#define REGISTER_ENTITY_TYPE(x) static bool x##Registration = \ +#define REGISTER_ENTITY_TYPE(x) bool x##Registration = \ EntityTypes::registerEntityType(EntityTypes::x, #x, x##EntityItem::factory); + +struct EntityRegistrationChecker { + EntityRegistrationChecker(bool result, const char* debugMessage) { + if (!result) { + qDebug() << debugMessage; + } + } +}; + /// Macro for registering entity types with an overloaded factory. Like using the REGISTER_ENTITY_TYPE macro: Make sure to add /// an element to the EntityType enum with your name. But unlike REGISTER_ENTITY_TYPE, your class can be named anything /// so long as you provide a static method passed to the macro, that takes an EnityItemID, and EntityItemProperties and @@ -79,9 +89,9 @@ private: // static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); #define REGISTER_ENTITY_TYPE_WITH_FACTORY(x,y) static bool x##Registration = \ EntityTypes::registerEntityType(EntityTypes::x, #x, y); \ - if (!x##Registration) { \ - qDebug() << "UNEXPECTED: REGISTER_ENTITY_TYPE_WITH_FACTORY(" #x "," #y ") FAILED.!"; \ - } + EntityRegistrationChecker x##RegistrationChecker( \ + x##Registration, \ + "UNEXPECTED: REGISTER_ENTITY_TYPE_WITH_FACTORY(" #x "," #y ") FAILED.!"); #endif // hifi_EntityTypes_h diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 4d63562cf7..6a5ef20bac 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -60,7 +60,7 @@ class LineEntityItem : public EntityItem { const QVector& getLinePoints() const{ return _points; } - virtual ShapeType getShapeType() const { return SHAPE_TYPE_LINE; } + virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } // never have a ray intersection pick a LineEntityItem. virtual bool supportsDetailedRayIntersection() const { return true; } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 40faf2c3c3..8e925b2f79 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -77,7 +77,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotationsSet, setJointRotationsSet); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotations, setJointRotations); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet); @@ -145,7 +145,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, dataAt += bytesFromAnimation; } - READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType); + READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); if (animationPropertiesChanged) { _dirtyFlags |= Simulation::DIRTY_UPDATEABLE; @@ -257,37 +257,54 @@ void ModelEntityItem::debugDump() const { qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL(); } -void ModelEntityItem::updateShapeType(ShapeType type) { - // BEGIN_TEMPORARY_WORKAROUND - // we have allowed inconsistent ShapeType's to be stored in SVO files in the past (this was a bug) - // but we are now enforcing the entity properties to be consistent. To make the possible we're - // introducing a temporary workaround: we will ignore ShapeType updates that conflict with the - // _compoundShapeURL. - if (hasCompoundShapeURL()) { - type = SHAPE_TYPE_COMPOUND; - } - // END_TEMPORARY_WORKAROUND - +void ModelEntityItem::setShapeType(ShapeType type) { if (type != _shapeType) { + if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) { + // dynamic and STATIC_MESH are incompatible + // since the shape is being set here we clear the dynamic bit + _dynamic = false; + _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; + } _shapeType = type; _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; } } -// virtual ShapeType ModelEntityItem::getShapeType() const { - if (_shapeType == SHAPE_TYPE_COMPOUND) { - return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE; - } else { - return _shapeType; + return computeTrueShapeType(); +} + +ShapeType ModelEntityItem::computeTrueShapeType() const { + ShapeType type = _shapeType; + if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) { + // dynamic is incompatible with STATIC_MESH + // shouldn't fall in here but just in case --> fall back to COMPOUND + type = SHAPE_TYPE_COMPOUND; + } + if (type == SHAPE_TYPE_COMPOUND && !hasCompoundShapeURL()) { + // no compoundURL set --> fall back to NONE + type = SHAPE_TYPE_NONE; + } + return type; +} + +void ModelEntityItem::setModelURL(const QString& url) { + if (_modelURL != url) { + _modelURL = url; + _parsedModelURL = QUrl(url); + if (_shapeType == SHAPE_TYPE_STATIC_MESH) { + _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; + } } } void ModelEntityItem::setCompoundShapeURL(const QString& url) { if (_compoundShapeURL != url) { + ShapeType oldType = computeTrueShapeType(); _compoundShapeURL = url; - _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; - _shapeType = _compoundShapeURL.isEmpty() ? SHAPE_TYPE_NONE : SHAPE_TYPE_COMPOUND; + if (oldType != computeTrueShapeType()) { + _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; + } } } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 29730bf4df..7b7edaf945 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -50,9 +50,10 @@ public: virtual bool needsToCallUpdate() const; virtual void debugDump() const; - void updateShapeType(ShapeType type); + void setShapeType(ShapeType type); virtual ShapeType getShapeType() const; + // TODO: Move these to subclasses, or other appropriate abstraction // getters/setters applicable to models and particles @@ -76,7 +77,7 @@ public: } // model related properties - virtual void setModelURL(const QString& url) { _modelURL = url; _parsedModelURL = QUrl(url); } + virtual void setModelURL(const QString& url); virtual void setCompoundShapeURL(const QString& url); // Animation related items... @@ -130,6 +131,7 @@ public: private: void setAnimationSettings(const QString& value); // only called for old bitstream format + ShapeType computeTrueShapeType() const; protected: // these are used: diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index a7bd0038e6..c501737146 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -342,7 +342,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan); SET_ENTITY_PROPERTY_FROM_PROPERTIES(isEmitting, setIsEmitting); @@ -406,7 +406,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, bool, setIsEmitting); } - READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType); + READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles); READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan); READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate); @@ -584,7 +584,7 @@ void ParticleEffectEntityItem::debugDump() const { qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); } -void ParticleEffectEntityItem::updateShapeType(ShapeType type) { +void ParticleEffectEntityItem::setShapeType(ShapeType type) { if (type != _shapeType) { _shapeType = type; _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 4538a1bb43..9ddda62c8b 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -95,7 +95,7 @@ public: void setAlphaSpread(float alphaSpread); float getAlphaSpread() const { return _alphaSpread; } - void updateShapeType(ShapeType type); + void setShapeType(ShapeType type); virtual ShapeType getShapeType() const { return _shapeType; } virtual void debugDump() const; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index cf7b14dbf1..3231e7c5e1 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -78,7 +78,7 @@ class PolyLineEntityItem : public EntityItem { virtual bool needsToCallUpdate() const { return true; } - virtual ShapeType getShapeType() const { return SHAPE_TYPE_LINE; } + virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } // never have a ray intersection pick a PolyLineEntityItem. virtual bool supportsDetailedRayIntersection() const { return true; } diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp new file mode 100644 index 0000000000..141526643c --- /dev/null +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -0,0 +1,232 @@ +// +// Created by Bradley Austin Davis on 2016/05/09 +// 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 "EntitiesLogging.h" +#include "EntityItemProperties.h" +#include "EntityTree.h" +#include "EntityTreeElement.h" +#include "ShapeEntityItem.h" + +namespace entity { + static const std::array shapeStrings { { + "Triangle", + "Quad", + "Hexagon", + "Octagon", + "Circle", + "Cube", + "Sphere", + "Tetrahedron", + "Octahedron", + "Dodecahedron", + "Icosahedron", + "Torus", + "Cone", + "Cylinder" + } }; + + Shape shapeFromString(const ::QString& shapeString) { + for (size_t i = 0; i < shapeStrings.size(); ++i) { + if (shapeString.toLower() == shapeStrings[i].toLower()) { + return static_cast(i); + } + } + return Shape::Sphere; + } + + ::QString stringFromShape(Shape shape) { + return shapeStrings[shape]; + } +} + +ShapeEntityItem::Pointer ShapeEntityItem::baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + Pointer entity { new ShapeEntityItem(entityID) }; + entity->setProperties(properties); + return entity; +} + +EntityItemPointer ShapeEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return baseFactory(entityID, properties); +} + +EntityItemPointer ShapeEntityItem::boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + auto result = baseFactory(entityID, properties); + result->setShape(entity::Shape::Cube); + return result; +} + +EntityItemPointer ShapeEntityItem::sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + auto result = baseFactory(entityID, properties); + result->setShape(entity::Shape::Sphere); + return result; +} + +// our non-pure virtual subclass for now... +ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + _type = EntityTypes::Shape; + _volumeMultiplier *= PI / 6.0f; +} + +EntityItemProperties ShapeEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { + EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class + properties.setColor(getXColor()); + properties.setShape(entity::stringFromShape(getShape())); + return properties; +} + +void ShapeEntityItem::setShape(const entity::Shape& shape) { + _shape = shape; + switch (_shape) { + case entity::Shape::Cube: + _type = EntityTypes::Box; + break; + case entity::Shape::Sphere: + _type = EntityTypes::Sphere; + break; + default: + _type = EntityTypes::Shape; + break; + } +} + +bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "ShapeEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_SHAPE, QString, setShape); + READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); + READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + + return bytesRead; +} + + +// TODO: eventually only include properties changed since the params.lastViewFrustumSent time +EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + requestedProperties += PROP_SHAPE; + requestedProperties += PROP_COLOR; + requestedProperties += PROP_ALPHA; + return requestedProperties; +} + +void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); + APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); +} + +// This value specifes how the shape should be treated by physics calculations. +// For now, all polys will act as spheres +ShapeType ShapeEntityItem::getShapeType() const { + return (_shape == entity::Shape::Cube) ? SHAPE_TYPE_BOX : SHAPE_TYPE_SPHERE; +} + +void ShapeEntityItem::setColor(const rgbColor& value) { + memcpy(_color, value, sizeof(rgbColor)); +} + +xColor ShapeEntityItem::getXColor() const { + return xColor { _color[0], _color[1], _color[2] }; +} + +void ShapeEntityItem::setColor(const xColor& value) { + setColor(rgbColor { value.red, value.green, value.blue }); +} + +QColor ShapeEntityItem::getQColor() const { + auto& color = getColor(); + return QColor(color[0], color[1], color[2], (int)(getAlpha() * 255)); +} + +void ShapeEntityItem::setColor(const QColor& value) { + setColor(rgbColor { (uint8_t)value.red(), (uint8_t)value.green(), (uint8_t)value.blue() }); + setAlpha(value.alpha()); +} + +bool ShapeEntityItem::supportsDetailedRayIntersection() const { + return _shape == entity::Sphere; +} + +bool ShapeEntityItem::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 { + // determine the ray in the frame of the entity transformed from a unit sphere + glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f))); + + float localDistance; + // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 + if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, localDistance)) { + // determine where on the unit sphere the hit point occured + glm::vec3 entityFrameHitAt = entityFrameOrigin + (entityFrameDirection * localDistance); + // then translate back to work coordinates + glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); + distance = glm::distance(origin, hitAt); + bool success; + surfaceNormal = glm::normalize(hitAt - getCenterPosition(success)); + if (!success) { + return false; + } + return true; + } + return false; +} + +void ShapeEntityItem::debugDump() const { + quint64 now = usecTimestampNow(); + qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------"; + qCDebug(entities) << " shape:" << stringFromShape(_shape); + qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; + qCDebug(entities) << " position:" << debugTreeVector(getPosition()); + qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); +} + diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h new file mode 100644 index 0000000000..122fc98dc0 --- /dev/null +++ b/libraries/entities/src/ShapeEntityItem.h @@ -0,0 +1,105 @@ +// +// Created by Bradley Austin Davis on 2016/05/09 +// 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_ShapeEntityItem_h +#define hifi_ShapeEntityItem_h + +#include "EntityItem.h" + +namespace entity { + enum Shape { + Triangle, + Quad, + Hexagon, + Octagon, + Circle, + Cube, + Sphere, + Tetrahedron, + Octahedron, + Dodecahedron, + Icosahedron, + Torus, + Cone, + Cylinder, + NUM_SHAPES, + }; + + Shape shapeFromString(const ::QString& shapeString); + ::QString stringFromShape(Shape shape); +} + + +class ShapeEntityItem : public EntityItem { + using Pointer = std::shared_ptr; + static Pointer baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties); +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + static EntityItemPointer sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties); + static EntityItemPointer boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties); + + ShapeEntityItem(const EntityItemID& entityItemID); + + void pureVirtualFunctionPlaceHolder() override { }; + // Triggers warnings on OSX + //ALLOW_INSTANTIATION + + // methods for getting/setting all properties of an entity + EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; + bool setProperties(const EntityItemProperties& properties) override; + + EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + entity::Shape getShape() const { return _shape; } + void setShape(const entity::Shape& shape); + void setShape(const QString& shape) { setShape(entity::shapeFromString(shape)); } + + float getAlpha() const { return _alpha; }; + void setAlpha(float alpha) { _alpha = alpha; } + + const rgbColor& getColor() const { return _color; } + void setColor(const rgbColor& value); + + xColor getXColor() const; + void setColor(const xColor& value); + + QColor getQColor() const; + void setColor(const QColor& value); + + ShapeType getShapeType() const override; + bool shouldBePhysical() const override { return !isDead(); } + + bool supportsDetailedRayIntersection() const override; + 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 override; + + void debugDump() const override; + +protected: + + float _alpha { 1 }; + rgbColor _color; + entity::Shape _shape { entity::Shape::Sphere }; +}; + +#endif // hifi_ShapeEntityItem_h diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index 1afec426d7..5f940bbe25 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -27,6 +27,7 @@ const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; // When poking objects with scripts an observer will bid at SCRIPT_EDIT priority. const quint8 SCRIPT_GRAB_SIMULATION_PRIORITY = 0x80; const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; +const quint8 AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1; // PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar // which really just means: things that collide with it will be bid at a priority level one lower diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp deleted file mode 100644 index 7ad7b39f20..0000000000 --- a/libraries/entities/src/SphereEntityItem.cpp +++ /dev/null @@ -1,132 +0,0 @@ -// -// SphereEntityItem.cpp -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/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 "EntitiesLogging.h" -#include "EntityItemProperties.h" -#include "EntityTree.h" -#include "EntityTreeElement.h" -#include "SphereEntityItem.h" - -EntityItemPointer SphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity { new SphereEntityItem(entityID) }; - entity->setProperties(properties); - return entity; -} - -// our non-pure virtual subclass for now... -SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { - _type = EntityTypes::Sphere; - _volumeMultiplier *= PI / 6.0f; -} - -EntityItemProperties SphereEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { - EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - properties.setColor(getXColor()); - return properties; -} - -bool SphereEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - - SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); - - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "SphereEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties.getLastEdited()); - } - return somethingChanged; -} - -int SphereEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged) { - - int bytesRead = 0; - const unsigned char* dataAt = data; - - READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); - - return bytesRead; -} - - -// TODO: eventually only include properties changed since the params.lastViewFrustumSent time -EntityPropertyFlags SphereEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { - EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_COLOR; - return requestedProperties; -} - -void SphereEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); -} - -bool SphereEntityItem::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 { - // determine the ray in the frame of the entity transformed from a unit sphere - glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); - glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); - glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); - glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f))); - - float localDistance; - // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 - if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, localDistance)) { - // determine where on the unit sphere the hit point occured - glm::vec3 entityFrameHitAt = entityFrameOrigin + (entityFrameDirection * localDistance); - // then translate back to work coordinates - glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); - distance = glm::distance(origin, hitAt); - bool success; - surfaceNormal = glm::normalize(hitAt - getCenterPosition(success)); - if (!success) { - return false; - } - return true; - } - return false; -} - - -void SphereEntityItem::debugDump() const { - quint64 now = usecTimestampNow(); - qCDebug(entities) << "SHPERE EntityItem id:" << getEntityItemID() << "---------------------------------------------"; - qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; - qCDebug(entities) << " position:" << debugTreeVector(getPosition()); - qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); -} - diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h deleted file mode 100644 index fda5eab009..0000000000 --- a/libraries/entities/src/SphereEntityItem.h +++ /dev/null @@ -1,70 +0,0 @@ -// -// SphereEntityItem.h -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/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_SphereEntityItem_h -#define hifi_SphereEntityItem_h - -#include "EntityItem.h" - -class SphereEntityItem : public EntityItem { -public: - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - SphereEntityItem(const EntityItemID& entityItemID); - - ALLOW_INSTANTIATION // This class can be instantiated - - // methods for getting/setting all properties of an entity - virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const; - virtual bool setProperties(const EntityItemProperties& properties); - - virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; - - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const; - - virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged); - - const rgbColor& getColor() const { return _color; } - xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } - - void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } - void setColor(const xColor& value) { - _color[RED_INDEX] = value.red; - _color[GREEN_INDEX] = value.green; - _color[BLUE_INDEX] = value.blue; - } - - virtual ShapeType getShapeType() const { return SHAPE_TYPE_SPHERE; } - virtual bool shouldBePhysical() const { return !isDead(); } - - virtual bool supportsDetailedRayIntersection() const { 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; - - virtual void debugDump() const; - -protected: - - rgbColor _color; -}; - -#endif // hifi_SphereEntityItem_h diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 6aabef5cc0..0b99d0377f 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -23,8 +23,12 @@ bool ZoneEntityItem::_zonesArePickable = false; bool ZoneEntityItem::_drawZoneBoundaries = false; + const ShapeType ZoneEntityItem::DEFAULT_SHAPE_TYPE = SHAPE_TYPE_BOX; const QString ZoneEntityItem::DEFAULT_COMPOUND_SHAPE_URL = ""; +const bool ZoneEntityItem::DEFAULT_FLYING_ALLOWED = true; +const bool ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED = true; + EntityItemPointer ZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity { new ZoneEntityItem(entityID) }; @@ -55,6 +59,9 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr _skyboxProperties.getProperties(properties); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed); + return properties; } @@ -66,10 +73,13 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChangedInStage = _stageProperties.setProperties(properties); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); + bool somethingChangedInSkybox = _skyboxProperties.setProperties(properties); somethingChanged = somethingChanged || somethingChangedInKeyLight || somethingChangedInStage || somethingChangedInSkybox; @@ -107,7 +117,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromStage; dataAt += bytesFromStage; - READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType); + READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); @@ -116,6 +126,9 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromSkybox; dataAt += bytesFromSkybox; + READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); + READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); + return bytesRead; } @@ -125,13 +138,16 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += _keyLightProperties.getEntityProperties(params); - + requestedProperties += PROP_SHAPE_TYPE; requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_BACKGROUND_MODE; requestedProperties += _stageProperties.getEntityProperties(params); requestedProperties += _skyboxProperties.getEntityProperties(params); - + + requestedProperties += PROP_FLYING_ALLOWED; + requestedProperties += PROP_GHOSTING_ALLOWED; + return requestedProperties; } @@ -155,10 +171,12 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, (uint32_t)getBackgroundMode()); // could this be a uint16?? - + _skyboxProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed()); + APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed()); } void ZoneEntityItem::debugDump() const { diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 0326a0f441..f0f2a91d63 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -55,7 +55,7 @@ public: static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; } virtual bool isReadyToComputeShape() { return false; } - void updateShapeType(ShapeType type) { _shapeType = type; } + void setShapeType(ShapeType type) { _shapeType = type; } virtual ShapeType getShapeType() const; virtual bool hasCompoundShapeURL() const { return !_compoundShapeURL.isEmpty(); } @@ -70,6 +70,11 @@ public: const SkyboxPropertyGroup& getSkyboxProperties() const { return _skyboxProperties; } const StagePropertyGroup& getStageProperties() const { return _stageProperties; } + bool getFlyingAllowed() const { return _flyingAllowed; } + void setFlyingAllowed(bool value) { _flyingAllowed = value; } + bool getGhostingAllowed() const { return _ghostingAllowed; } + void setGhostingAllowed(bool value) { _ghostingAllowed = value; } + virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, @@ -80,18 +85,23 @@ public: static const ShapeType DEFAULT_SHAPE_TYPE; static const QString DEFAULT_COMPOUND_SHAPE_URL; - + static const bool DEFAULT_FLYING_ALLOWED; + static const bool DEFAULT_GHOSTING_ALLOWED; + protected: KeyLightPropertyGroup _keyLightProperties; - + ShapeType _shapeType = DEFAULT_SHAPE_TYPE; QString _compoundShapeURL; - + BackgroundMode _backgroundMode = BACKGROUND_MODE_INHERIT; StagePropertyGroup _stageProperties; SkyboxPropertyGroup _skyboxProperties; + bool _flyingAllowed { DEFAULT_FLYING_ALLOWED }; + bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED }; + static bool _drawZoneBoundaries; static bool _zonesArePickable; }; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 65cae5343a..6afaf933f2 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -39,7 +39,7 @@ using namespace std; -static int FBXGeometryPointerMetaTypeId = qRegisterMetaType(); +int FBXGeometryPointerMetaTypeId = qRegisterMetaType(); QStringList FBXGeometry::getJointNames() const { QStringList names; @@ -130,9 +130,9 @@ QString FBXGeometry::getModelNameOfMesh(int meshIndex) const { return QString(); } -static int fbxGeometryMetaTypeId = qRegisterMetaType(); -static int fbxAnimationFrameMetaTypeId = qRegisterMetaType(); -static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); +int fbxGeometryMetaTypeId = qRegisterMetaType(); +int fbxAnimationFrameMetaTypeId = qRegisterMetaType(); +int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); glm::vec3 parseVec3(const QString& string) { @@ -462,7 +462,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS QVector blendshapes; QHash models; - QHash clusters; + QHash clusters; QHash animationCurves; QHash typeFlags; @@ -759,7 +759,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS model.preTransform = glm::translate(rotationOffset) * glm::translate(rotationPivot); model.preRotation = glm::quat(glm::radians(preRotation)); model.rotation = glm::quat(glm::radians(rotation)); - model.postRotation = glm::quat(glm::radians(postRotation)); + model.postRotation = glm::inverse(glm::quat(glm::radians(postRotation))); model.postTransform = glm::translate(-rotationPivot) * glm::translate(scaleOffset) * glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot); // NOTE: angles from the FBX file are in degrees @@ -927,6 +927,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS // material.emissiveColor = getVec3(property.properties, index); // material.emissiveFactor = 1.0; + } else if (property.properties.at(0) == "AmbientFactor") { + material.ambientFactor = property.properties.at(index).value(); + // Detected just for BLender AO vs lightmap } else if (property.properties.at(0) == "Shininess") { material.shininess = property.properties.at(index).value(); @@ -1128,8 +1131,10 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS emissiveTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type.contains("tex_emissive_map")) { emissiveTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } else if (type.contains("ambient")) { + } else if (type.contains("ambientcolor")) { ambientTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type.contains("ambientfactor")) { + ambientFactorTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type.contains("tex_ao_map")) { occlusionTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type == "lcl rotation") { @@ -1361,7 +1366,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS geometry.meshExtents.reset(); // Create the Material Library - consolidateFBXMaterials(); + consolidateFBXMaterials(mapping); geometry.materials = _fbxMaterials; // see if any materials have texture children diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 6ba1c5786b..d2d433d53e 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -151,6 +151,7 @@ public: float metallic{ 0.0f }; float roughness{ 1.0f }; float emissiveIntensity{ 1.0f }; + float ambientFactor{ 1.0f }; QString materialID; QString name; @@ -166,6 +167,7 @@ public: FBXTexture metallicTexture; FBXTexture emissiveTexture; FBXTexture occlusionTexture; + FBXTexture scatteringTexture; FBXTexture lightmapTexture; glm::vec2 lightmapParams{ 0.0f, 1.0f }; @@ -437,11 +439,12 @@ public: QHash shininessTextures; QHash emissiveTextures; QHash ambientTextures; + QHash ambientFactorTextures; QHash occlusionTextures; QHash _fbxMaterials; - void consolidateFBXMaterials(); + void consolidateFBXMaterials(const QVariantHash& mapping); bool _loadLightmaps = true; float _lightmapOffset = 0.0f; diff --git a/libraries/fbx/src/FBXReader_Material.cpp b/libraries/fbx/src/FBXReader_Material.cpp index 22a577072d..eb25f1d8a2 100644 --- a/libraries/fbx/src/FBXReader_Material.cpp +++ b/libraries/fbx/src/FBXReader_Material.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include "FBXReader.h" #include @@ -67,9 +69,13 @@ FBXTexture FBXReader::getTexture(const QString& textureID) { return texture; } -void FBXReader::consolidateFBXMaterials() { - - // foreach (const QString& materialID, materials) { +void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { + + QString materialMapString = mapping.value("materialMap").toString(); + QJsonDocument materialMapDocument = QJsonDocument::fromJson(materialMapString.toUtf8()); + QJsonObject materialMap = materialMapDocument.object(); + + // foreach (const QString& materialID, materials) { for (QHash::iterator it = _fbxMaterials.begin(); it != _fbxMaterials.end(); it++) { FBXMaterial& material = (*it); @@ -186,6 +192,14 @@ void FBXReader::consolidateFBXMaterials() { FBXTexture occlusionTexture; QString occlusionTextureID = occlusionTextures.value(material.materialID); + if (occlusionTextureID.isNull()) { + // 2nd chance + // For blender we use the ambient factor texture as AOMap ONLY if the ambientFactor value is > 0.0 + if (material.ambientFactor > 0.0f) { + occlusionTextureID = ambientFactorTextures.value(material.materialID); + } + } + if (!occlusionTextureID.isNull()) { occlusionTexture = getTexture(occlusionTextureID); detectDifferentUVs |= (occlusionTexture.texcoordSet != 0) || (!emissiveTexture.transform.isIdentity()); @@ -198,6 +212,14 @@ void FBXReader::consolidateFBXMaterials() { FBXTexture ambientTexture; QString ambientTextureID = ambientTextures.value(material.materialID); + if (ambientTextureID.isNull()) { + // 2nd chance + // For blender we use the ambient factor texture as Lightmap ONLY if the ambientFactor value is set to 0 + if (material.ambientFactor == 0.0f) { + ambientTextureID = ambientFactorTextures.value(material.materialID); + } + } + if (_loadLightmaps && !ambientTextureID.isNull()) { ambientTexture = getTexture(ambientTextureID); detectDifferentUVs |= (ambientTexture.texcoordSet != 0) || (!ambientTexture.transform.isIdentity()); @@ -236,6 +258,24 @@ void FBXReader::consolidateFBXMaterials() { } } } + qDebug() << " fbx material Name:" << material.name; + + if (materialMap.contains(material.name)) { + QJsonObject materialOptions = materialMap.value(material.name).toObject(); + qDebug() << "Mapping fbx material:" << material.name << " with HifiMaterial: " << materialOptions; + + if (materialOptions.contains("scattering")) { + float scattering = (float) materialOptions.value("scattering").toDouble(); + material._material->setScattering(scattering); + } + + if (materialOptions.contains("scatteringMap")) { + QByteArray scatteringMap = materialOptions.value("scatteringMap").toVariant().toByteArray(); + material.scatteringTexture = FBXTexture(); + material.scatteringTexture.name = material.name + ".scatteringMap"; + material.scatteringTexture.filename = scatteringMap; + } + } if (material.opacity <= 0.0f) { material._material->setOpacity(1.0f); diff --git a/libraries/gl/src/gl/Config.h b/libraries/gl/src/gl/Config.h index 593537a291..7947bd45df 100644 --- a/libraries/gl/src/gl/Config.h +++ b/libraries/gl/src/gl/Config.h @@ -20,6 +20,7 @@ #include #include +#include #endif diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 302e0b8515..2781d5b9b0 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -5,6 +5,15 @@ #include #include #include +#include +#include +#ifdef DEBUG +static bool enableDebug = true; +#else +static const QString DEBUG_FLAG("HIFI_ENABLE_OPENGL_45"); +static bool enableDebug = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); +#endif + const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { static QSurfaceFormat format; @@ -14,9 +23,9 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); setGLFormatVersion(format); -#ifdef DEBUG - format.setOption(QSurfaceFormat::DebugContext); -#endif + if (enableDebug) { + format.setOption(QSurfaceFormat::DebugContext); + } format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); QSurfaceFormat::setDefaultFormat(format); }); @@ -39,6 +48,13 @@ const QGLFormat& getDefaultGLFormat() { return glFormat; } +int glVersionToInteger(QString glVersion) { + QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); + int majorNumber = versionParts[0].toInt(); + int minorNumber = versionParts[1].toInt(); + return majorNumber * 100 + minorNumber * 10; +} + QJsonObject getGLContextData() { if (!QOpenGLContext::currentContext()) { return QJsonObject(); diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index ddb254f1c5..477bf7abc8 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -27,5 +27,6 @@ void setGLFormatVersion(F& format, int major = 4, int minor = 5) { format.setVer const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); const QGLFormat& getDefaultGLFormat(); QJsonObject getGLContextData(); +int glVersionToInteger(QString glVersion); #endif diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index f113be1cfb..8b0bd9981f 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -43,17 +43,21 @@ int GLWidget::getDeviceHeight() const { void GLWidget::initializeGL() { setAttribute(Qt::WA_AcceptTouchEvents); + grabGesture(Qt::PinchGesture); 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) if (isValid() && context() && context()->contextHandle()) { - _vsyncSupported = context()->contextHandle()->hasExtension("WGL_EXT_swap_control");; - } +#if defined(Q_OS_WIN) + _vsyncSupported = context()->contextHandle()->hasExtension("WGL_EXT_swap_control"); +#elif defined(Q_OS_MAC) + _vsyncSupported = true; +#else + // TODO: write the proper code for linux #endif + } } void GLWidget::paintEvent(QPaintEvent* event) { @@ -78,6 +82,7 @@ bool GLWidget::event(QEvent* event) { case QEvent::TouchBegin: case QEvent::TouchEnd: case QEvent::TouchUpdate: + case QEvent::Gesture: case QEvent::Wheel: case QEvent::DragEnter: case QEvent::Drop: diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 90ff369cd6..a6b5a03ff6 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -12,24 +12,39 @@ #include "OffscreenGLCanvas.h" +#include #include #include #include #include "GLHelpers.h" +#ifdef DEBUG +static bool enableDebugLogger = true; +#else +static const QString DEBUG_FLAG("HIFI_ENABLE_OPENGL_45"); +static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); +#endif + + OffscreenGLCanvas::OffscreenGLCanvas() : _context(new QOpenGLContext), _offscreenSurface(new QOffscreenSurface){ } OffscreenGLCanvas::~OffscreenGLCanvas() { -#ifdef DEBUG if (_logger) { makeCurrent(); delete _logger; _logger = nullptr; } -#endif + _context->doneCurrent(); + delete _context; + _context = nullptr; + + _offscreenSurface->destroy(); + delete _offscreenSurface; + _offscreenSurface = nullptr; + } bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { @@ -60,7 +75,7 @@ bool OffscreenGLCanvas::makeCurrent() { qDebug() << "GL Renderer: " << QString((const char*) glGetString(GL_RENDERER)); }); -#ifdef DEBUG + if (result && !_logger) { _logger = new QOpenGLDebugLogger(this); if (_logger->initialize()) { @@ -71,7 +86,6 @@ bool OffscreenGLCanvas::makeCurrent() { _logger->startLogging(QOpenGLDebugLogger::LoggingMode::SynchronousLogging); } } -#endif return result; } diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.h b/libraries/gl/src/gl/OffscreenGLCanvas.h index 387804bf56..8def09796d 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.h +++ b/libraries/gl/src/gl/OffscreenGLCanvas.h @@ -34,12 +34,9 @@ public: protected: std::once_flag _reportOnce; - QOpenGLContext* _context; - QOffscreenSurface* _offscreenSurface; -#ifdef DEBUG + QOpenGLContext* _context{ nullptr }; + QOffscreenSurface* _offscreenSurface{ nullptr }; QOpenGLDebugLogger* _logger{ nullptr }; -#endif - }; #endif // hifi_OffscreenGLCanvas_h diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index e90ef9e782..b4a9080ee8 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -396,6 +396,8 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { _renderer->_renderControl->_renderWindow = _proxyWindow; + connect(_renderer->_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); + // Create a QML engine. _qmlEngine = new QQmlEngine; if (!_qmlEngine->incubationController()) { @@ -414,7 +416,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { _updateTimer.start(); } -void OffscreenQmlSurface::resize(const QSize& newSize_) { +void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { if (!_renderer || !_renderer->_quickWindow) { return; @@ -433,7 +435,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_) { } QSize currentSize = _renderer->_quickWindow->geometry().size(); - if (newSize == currentSize) { + if (newSize == currentSize && !forceResize) { return; } @@ -716,9 +718,9 @@ QQmlContext* OffscreenQmlSurface::getRootContext() { } Q_DECLARE_METATYPE(std::function); -static auto VoidLambdaType = qRegisterMetaType>(); +auto VoidLambdaType = qRegisterMetaType>(); Q_DECLARE_METATYPE(std::function); -static auto VariantLambdaType = qRegisterMetaType>(); +auto VariantLambdaType = qRegisterMetaType>(); void OffscreenQmlSurface::executeOnUiThread(std::function function, bool blocking ) { @@ -742,3 +744,21 @@ QVariant OffscreenQmlSurface::returnFromUiThread(std::function funct return function(); } + +void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { + if (!object) { + setFocusText(false); + return; + } + + QInputMethodQueryEvent query(Qt::ImEnabled); + qApp->sendEvent(object, &query); + setFocusText(query.value(Qt::ImEnabled).toBool()); +} + +void OffscreenQmlSurface::setFocusText(bool newFocusText) { + if (newFocusText != _focusText) { + _focusText = newFocusText; + emit focusTextChanged(_focusText); + } +} diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index 22a1b99fe6..a4a5ecba7e 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -30,7 +30,7 @@ class OffscreenQmlRenderThread; class OffscreenQmlSurface : public QObject { Q_OBJECT - + Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) public: OffscreenQmlSurface(); virtual ~OffscreenQmlSurface(); @@ -38,7 +38,7 @@ public: using MouseTranslator = std::function; virtual void create(QOpenGLContext* context); - void resize(const QSize& size); + void resize(const QSize& size, bool forceResize = false); QSize size() const; Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {}); Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function f = [](QQmlContext*, QObject*) {}) { @@ -55,6 +55,7 @@ public: _mouseTranslator = mouseTranslator; } + bool isFocusText() const { return _focusText; } void pause(); void resume(); bool isPaused() const; @@ -70,6 +71,8 @@ public: signals: void textureUpdated(unsigned int texture); + void focusObjectChanged(QObject* newFocus); + void focusTextChanged(bool focusText); public slots: void requestUpdate(); @@ -78,6 +81,7 @@ public slots: protected: bool filterEnabled(QObject* originalDestination, QEvent* event) const; + void setFocusText(bool newFocusText); private: QObject* finishQmlLoad(std::function f); @@ -85,6 +89,7 @@ private: private slots: void updateQuick(); + void onFocusObjectChanged(QObject* newFocus); private: friend class OffscreenQmlRenderThread; @@ -97,6 +102,7 @@ private: bool _render{ false }; bool _polish{ true }; bool _paused{ true }; + bool _focusText { false }; uint8_t _maxFps{ 60 }; MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } }; QWindow* _proxyWindow { nullptr }; diff --git a/libraries/gl/src/gl/OglplusHelpers.cpp b/libraries/gl/src/gl/OglplusHelpers.cpp index 5bf0298593..1154042b4a 100644 --- a/libraries/gl/src/gl/OglplusHelpers.cpp +++ b/libraries/gl/src/gl/OglplusHelpers.cpp @@ -45,9 +45,11 @@ in vec2 vTexCoord; out vec4 FragColor; void main() { - FragColor = texture(sampler, vTexCoord); FragColor.a *= alpha; + if (FragColor.a <= 0.0) { + discard; + } } )FS"; @@ -85,6 +87,36 @@ ProgramPtr loadCubemapShader() { return result; } +void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& gs, const std::string& fs) { + using namespace oglplus; + try { + result = std::make_shared(); + // attach the shaders to the program + result->AttachShader( + VertexShader() + .Source(GLSLSource(vs)) + .Compile() + ); + result->AttachShader( + GeometryShader() + .Source(GLSLSource(gs)) + .Compile() + ); + result->AttachShader( + FragmentShader() + .Source(GLSLSource(fs)) + .Compile() + ); + result->Link(); + } catch (ProgramBuildError& err) { + Q_UNUSED(err); + qWarning() << err.Log().c_str(); + Q_ASSERT_X(false, "compileProgram", "Failed to build shader program"); + qFatal("%s", (const char*)err.Message); + result.reset(); + } +} + void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs) { using namespace oglplus; try { @@ -359,6 +391,94 @@ ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov, float aspect, i ); } +namespace oglplus { + namespace shapes { + + class Laser : public DrawingInstructionWriter, public DrawMode { + public: + using IndexArray = std::vector; + using PosArray = std::vector; + /// The type of the index container returned by Indices() + // vertex positions + PosArray _pos_data; + IndexArray _idx_data; + unsigned int _prim_count { 0 }; + + public: + Laser() { + int vertices = 2; + _pos_data.resize(vertices * 3); + _pos_data[0] = 0; + _pos_data[1] = 0; + _pos_data[2] = 0; + + _pos_data[3] = 0; + _pos_data[4] = 0; + _pos_data[5] = -1; + + _idx_data.push_back(0); + _idx_data.push_back(1); + _prim_count = 1; + } + + /// Returns the winding direction of faces + FaceOrientation FaceWinding(void) const { + return FaceOrientation::CCW; + } + + /// Queries the bounding sphere coordinates and dimensions + template + void BoundingSphere(Sphere& bounding_sphere) const { + bounding_sphere = Sphere(0, 0, -0.5, 0.5); + } + + typedef GLuint(Laser::*VertexAttribFunc)(std::vector&) const; + + /// Makes the vertex positions and returns the number of values per vertex + template + GLuint Positions(std::vector& dest) const { + dest.clear(); + dest.insert(dest.begin(), _pos_data.begin(), _pos_data.end()); + return 3; + } + + typedef VertexAttribsInfo< + Laser, + std::tuple + > VertexAttribs; + + + /// Returns element indices that are used with the drawing instructions + const IndexArray & Indices(Default = Default()) const { + return _idx_data; + } + + /// Returns the instructions for rendering of faces + DrawingInstructions Instructions(PrimitiveType primitive) const { + DrawingInstructions instr = MakeInstructions(); + DrawOperation operation; + operation.method = DrawOperation::Method::DrawElements; + operation.mode = primitive; + operation.first = 0; + operation.count = _prim_count * 3; + operation.restart_index = DrawOperation::NoRestartIndex(); + operation.phase = 0; + AddInstruction(instr, operation); + return instr; + } + + /// Returns the instructions for rendering of faces + DrawingInstructions Instructions(Default = Default()) const { + return Instructions(PrimitiveType::Lines); + } + }; + } +} + +ShapeWrapperPtr loadLaser(const ProgramPtr& program) { + return std::make_shared(shapes::ShapeWrapper("Position", shapes::Laser(), *program)); +} + void TextureRecycler::setSize(const uvec2& size) { if (size == _size) { return; diff --git a/libraries/gl/src/gl/OglplusHelpers.h b/libraries/gl/src/gl/OglplusHelpers.h index afb06069b8..c453fbad28 100644 --- a/libraries/gl/src/gl/OglplusHelpers.h +++ b/libraries/gl/src/gl/OglplusHelpers.h @@ -62,10 +62,13 @@ using Mat4Uniform = oglplus::Uniform; ProgramPtr loadDefaultShader(); ProgramPtr loadCubemapShader(); void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs); +void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& gs, const std::string& fs); + ShapeWrapperPtr loadSkybox(ProgramPtr program); ShapeWrapperPtr loadPlane(ProgramPtr program, float aspect = 1.0f); -ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 32, int stacks = 32); - +ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 128, int stacks = 128); +ShapeWrapperPtr loadLaser(const ProgramPtr& program); + // A basic wrapper for constructing a framebuffer with a renderbuffer // for the depth attachment and an undefined type for the color attachement diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp index bc8afc3927..0b153a5ae8 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp @@ -28,16 +28,17 @@ QOpenGLContext* QOpenGLContextWrapper::currentContext() { return QOpenGLContext::currentContext(); } - QOpenGLContextWrapper::QOpenGLContextWrapper() : -_context(new QOpenGLContext) -{ -} - + _ownContext(true), _context(new QOpenGLContext) { } QOpenGLContextWrapper::QOpenGLContextWrapper(QOpenGLContext* context) : - _context(context) -{ + _context(context) { } + +QOpenGLContextWrapper::~QOpenGLContextWrapper() { + if (_ownContext) { + delete _context; + _context = nullptr; + } } void QOpenGLContextWrapper::setFormat(const QSurfaceFormat& format) { diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.h b/libraries/gl/src/gl/QOpenGLContextWrapper.h index d097284e68..b09ad1a4c3 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.h +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.h @@ -23,6 +23,7 @@ class QOpenGLContextWrapper { public: QOpenGLContextWrapper(); QOpenGLContextWrapper(QOpenGLContext* context); + virtual ~QOpenGLContextWrapper(); void setFormat(const QSurfaceFormat& format); bool create(); void swapBuffers(QSurface* surface); @@ -40,6 +41,7 @@ public: private: + bool _ownContext { false }; QOpenGLContext* _context { nullptr }; }; diff --git a/libraries/gl/src/gl/QOpenGLDebugLoggerWrapper.cpp b/libraries/gl/src/gl/QOpenGLDebugLoggerWrapper.cpp index bd185034f4..2a351ead7e 100644 --- a/libraries/gl/src/gl/QOpenGLDebugLoggerWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLDebugLoggerWrapper.cpp @@ -14,11 +14,16 @@ #include #include +void OpenGLDebug::log(const QOpenGLDebugMessage & debugMessage) { + qDebug() << debugMessage; +} + 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; + OpenGLDebug::log(debugMessage); + }); } \ No newline at end of file diff --git a/libraries/gl/src/gl/QOpenGLDebugLoggerWrapper.h b/libraries/gl/src/gl/QOpenGLDebugLoggerWrapper.h index e2b1c5d9d4..2a378a712a 100644 --- a/libraries/gl/src/gl/QOpenGLDebugLoggerWrapper.h +++ b/libraries/gl/src/gl/QOpenGLDebugLoggerWrapper.h @@ -13,7 +13,13 @@ #define hifi_QOpenGLDebugLoggerWrapper_h class QObject; +class QOpenGLDebugMessage; void setupDebugLogger(QObject* window); +class OpenGLDebug { +public: + static void log(const QOpenGLDebugMessage & debugMessage); +}; + #endif // hifi_QOpenGLDebugLoggerWrapper_h \ No newline at end of file diff --git a/libraries/gpu-gl/CMakeLists.txt b/libraries/gpu-gl/CMakeLists.txt index dfac6dd516..398fdd04d6 100644 --- a/libraries/gpu-gl/CMakeLists.txt +++ b/libraries/gpu-gl/CMakeLists.txt @@ -1,5 +1,4 @@ set(TARGET_NAME gpu-gl) -AUTOSCRIBE_SHADER_LIB(gpu) setup_hifi_library() link_hifi_libraries(shared gl gpu) GroupSources("src") diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 69699e673b..ce2f4c8d66 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -16,17 +16,48 @@ #include #include +#include "../gl41/GL41Backend.h" +#include "../gl45/GL45Backend.h" + #if defined(NSIGHT_FOUND) #include "nvToolsExt.h" #endif #include -#include -#include "GLBackendShared.h" +#include +#include +#include "GLTexture.h" +#include "GLShader.h" using namespace gpu; using namespace gpu::gl; +static const QString DEBUG_FLAG("HIFI_ENABLE_OPENGL_45"); +static bool enableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + +Backend* GLBackend::createBackend() { + // FIXME provide a mechanism to override the backend for testing + // Where the gpuContext is initialized and where the TRUE Backend is created and assigned + auto version = QOpenGLContextWrapper::currentContextVersion(); + GLBackend* result; + if (enableOpenGL45 && version >= 0x0405) { + qDebug() << "Using OpenGL 4.5 backend"; + result = new gpu::gl45::GL45Backend(); + } else { + qDebug() << "Using OpenGL 4.1 backend"; + result = new gpu::gl41::GL41Backend(); + } + result->initInput(); + result->initTransform(); + gl::GLTexture::initTextureTransferHelper(); + return result; +} + + +bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings) { + return GLShader::makeProgram(shader, slotBindings); +} + GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = { (&::gpu::gl::GLBackend::do_draw), @@ -80,6 +111,7 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_glUniform3fv), (&::gpu::gl::GLBackend::do_glUniform4fv), (&::gpu::gl::GLBackend::do_glUniform4iv), + (&::gpu::gl::GLBackend::do_glUniformMatrix3fv), (&::gpu::gl::GLBackend::do_glUniformMatrix4fv), (&::gpu::gl::GLBackend::do_glColor4f), @@ -95,16 +127,13 @@ void GLBackend::init() { std::call_once(once, [] { TEXTURE_ID_RESOLVER = [](const Texture& texture)->uint32 { - auto object = Backend::getGPUObject(texture); + auto object = Backend::getGPUObject(texture); if (!object) { return 0; } - if (object->getSyncState() != GLTexture::Idle) { - if (object->_downsampleSource) { - return object->_downsampleSource->_texture; - } - return 0; + if (object->getSyncState() != GLSyncState::Idle) { + return object->_downsampleSource._texture; } return object->_texture; }; @@ -148,61 +177,11 @@ void GLBackend::init() { }); } -Context::Size GLBackend::getDedicatedMemory() { - static Context::Size dedicatedMemory { 0 }; - static std::once_flag once; - std::call_once(once, [&] { -#ifdef Q_OS_WIN - if (!dedicatedMemory && wglGetGPUIDsAMD && wglGetGPUInfoAMD) { - UINT maxCount = wglGetGPUIDsAMD(0, 0); - std::vector ids; - ids.resize(maxCount); - wglGetGPUIDsAMD(maxCount, &ids[0]); - GLuint memTotal; - wglGetGPUInfoAMD(ids[0], WGL_GPU_RAM_AMD, GL_UNSIGNED_INT, sizeof(GLuint), &memTotal); - dedicatedMemory = MB_TO_BYTES(memTotal); - } -#endif - - if (!dedicatedMemory) { - GLint atiGpuMemory[4]; - // not really total memory, but close enough if called early enough in the application lifecycle - glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, atiGpuMemory); - if (GL_NO_ERROR == glGetError()) { - dedicatedMemory = KB_TO_BYTES(atiGpuMemory[0]); - } - } - - if (!dedicatedMemory) { - GLint nvGpuMemory { 0 }; - glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &nvGpuMemory); - if (GL_NO_ERROR == glGetError()) { - dedicatedMemory = KB_TO_BYTES(nvGpuMemory); - } - } - - if (!dedicatedMemory) { - auto gpuIdent = GPUIdent::getInstance(); - if (gpuIdent && gpuIdent->isValid()) { - dedicatedMemory = MB_TO_BYTES(gpuIdent->getMemory()); - } - } - }); - - return dedicatedMemory; -} - -Backend* GLBackend::createBackend() { - return new GLBackend(); -} - GLBackend::GLBackend() { glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &_uboAlignment); - initInput(); - initTransform(); - initTextureTransferHelper(); } + GLBackend::~GLBackend() { resetStages(); @@ -260,7 +239,7 @@ void GLBackend::renderPassTransfer(Batch& batch) { { // Sync the transform buffers PROFILE_RANGE("syncGPUTransform"); - _transform.transfer(batch); + transferTransformState(batch); } _inRenderTransferPass = false; @@ -355,164 +334,6 @@ void GLBackend::setupStereoSide(int side) { _transform.bindCurrentCamera(side); } - -void GLBackend::do_draw(Batch& batch, size_t paramOffset) { - Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; - GLenum mode = _primitiveToGLmode[primitiveType]; - uint32 numVertices = batch._params[paramOffset + 1]._uint; - uint32 startVertex = batch._params[paramOffset + 0]._uint; - - if (isStereo()) { - setupStereoSide(0); - glDrawArrays(mode, startVertex, numVertices); - setupStereoSide(1); - glDrawArrays(mode, startVertex, numVertices); - - _stats._DSNumTriangles += 2 * numVertices / 3; - _stats._DSNumDrawcalls += 2; - - } else { - glDrawArrays(mode, startVertex, numVertices); - _stats._DSNumTriangles += numVertices / 3; - _stats._DSNumDrawcalls++; - } - _stats._DSNumAPIDrawcalls++; - - (void) CHECK_GL_ERROR(); -} - -void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) { - Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; - GLenum mode = _primitiveToGLmode[primitiveType]; - uint32 numIndices = batch._params[paramOffset + 1]._uint; - uint32 startIndex = batch._params[paramOffset + 0]._uint; - - GLenum glType = _elementTypeToGLType[_input._indexBufferType]; - - auto typeByteSize = TYPE_SIZE[_input._indexBufferType]; - GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); - - if (isStereo()) { - setupStereoSide(0); - glDrawElements(mode, numIndices, glType, indexBufferByteOffset); - setupStereoSide(1); - glDrawElements(mode, numIndices, glType, indexBufferByteOffset); - - _stats._DSNumTriangles += 2 * numIndices / 3; - _stats._DSNumDrawcalls += 2; - } else { - glDrawElements(mode, numIndices, glType, indexBufferByteOffset); - _stats._DSNumTriangles += numIndices / 3; - _stats._DSNumDrawcalls++; - } - _stats._DSNumAPIDrawcalls++; - - (void) CHECK_GL_ERROR(); -} - -void GLBackend::do_drawInstanced(Batch& batch, size_t paramOffset) { - GLint numInstances = batch._params[paramOffset + 4]._uint; - Primitive primitiveType = (Primitive)batch._params[paramOffset + 3]._uint; - GLenum mode = _primitiveToGLmode[primitiveType]; - uint32 numVertices = batch._params[paramOffset + 2]._uint; - uint32 startVertex = batch._params[paramOffset + 1]._uint; - - - if (isStereo()) { - GLint trueNumInstances = 2 * numInstances; - - setupStereoSide(0); - glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); - setupStereoSide(1); - glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); - - _stats._DSNumTriangles += (trueNumInstances * numVertices) / 3; - _stats._DSNumDrawcalls += trueNumInstances; - } else { - glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); - _stats._DSNumTriangles += (numInstances * numVertices) / 3; - _stats._DSNumDrawcalls += numInstances; - } - _stats._DSNumAPIDrawcalls++; - - (void) CHECK_GL_ERROR(); -} - -void glbackend_glDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex, GLuint baseinstance) { -#if (GPU_INPUT_PROFILE == GPU_CORE_43) - glDrawElementsInstancedBaseVertexBaseInstance(mode, count, type, indices, primcount, basevertex, baseinstance); -#else - glDrawElementsInstanced(mode, count, type, indices, primcount); -#endif -} - -void GLBackend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { - GLint numInstances = batch._params[paramOffset + 4]._uint; - GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 3]._uint]; - uint32 numIndices = batch._params[paramOffset + 2]._uint; - uint32 startIndex = batch._params[paramOffset + 1]._uint; - // FIXME glDrawElementsInstancedBaseVertexBaseInstance is only available in GL 4.3 - // and higher, so currently we ignore this field - uint32 startInstance = batch._params[paramOffset + 0]._uint; - GLenum glType = _elementTypeToGLType[_input._indexBufferType]; - - auto typeByteSize = TYPE_SIZE[_input._indexBufferType]; - GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); - - if (isStereo()) { - GLint trueNumInstances = 2 * numInstances; - - setupStereoSide(0); - glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); - setupStereoSide(1); - glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); - - _stats._DSNumTriangles += (trueNumInstances * numIndices) / 3; - _stats._DSNumDrawcalls += trueNumInstances; - } else { - glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); - _stats._DSNumTriangles += (numInstances * numIndices) / 3; - _stats._DSNumDrawcalls += numInstances; - } - - _stats._DSNumAPIDrawcalls++; - - (void)CHECK_GL_ERROR(); -} - - -void GLBackend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { -#if (GPU_INPUT_PROFILE == GPU_CORE_43) - uint commandCount = batch._params[paramOffset + 0]._uint; - GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 1]._uint]; - - glMultiDrawArraysIndirect(mode, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); - _stats._DSNumDrawcalls += commandCount; - _stats._DSNumAPIDrawcalls++; - -#else - // FIXME implement the slow path -#endif - (void)CHECK_GL_ERROR(); - -} - -void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) { -#if (GPU_INPUT_PROFILE == GPU_CORE_43) - uint commandCount = batch._params[paramOffset + 0]._uint; - GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 1]._uint]; - GLenum indexType = _elementTypeToGLType[_input._indexBufferType]; - - glMultiDrawElementsIndirect(mode, indexType, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); - _stats._DSNumDrawcalls += commandCount; - _stats._DSNumAPIDrawcalls++; -#else - // FIXME implement the slow path -#endif - (void)CHECK_GL_ERROR(); -} - - void GLBackend::do_resetStages(Batch& batch, size_t paramOffset) { resetStages(); } @@ -543,41 +364,34 @@ void GLBackend::resetStages() { (void) CHECK_GL_ERROR(); } + +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, size_t paramOffset) { +#if defined(NSIGHT_FOUND) + nvtxRangePop(); +#endif +} + // 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 -#define ADD_COMMAND_GL(call) _commands.push_back(COMMAND_##call); _commandOffsets.push_back(_params.size()); - // As long as we don;t use several versions of shaders we can avoid this more complex code path // #define GET_UNIFORM_LOCATION(shaderUniformLoc) _pipeline._programShader->getUniformLocation(shaderUniformLoc, isStereo()); #define GET_UNIFORM_LOCATION(shaderUniformLoc) shaderUniformLoc - -void Batch::_glActiveBindTexture(GLenum unit, GLenum target, GLuint texture) { - // clean the cache on the texture unit we are going to use so the next call to setResourceTexture() at the same slot works fine - setResourceTexture(unit - GL_TEXTURE0, nullptr); - - ADD_COMMAND_GL(glActiveBindTexture); - _params.push_back(texture); - _params.push_back(target); - _params.push_back(unit); -} void GLBackend::do_glActiveBindTexture(Batch& batch, size_t paramOffset) { glActiveTexture(batch._params[paramOffset + 2]._uint); glBindTexture( GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._uint), batch._params[paramOffset + 0]._uint); - (void) CHECK_GL_ERROR(); -} - -void Batch::_glUniform1i(GLint location, GLint v0) { - if (location < 0) { - return; - } - ADD_COMMAND_GL(glUniform1i); - _params.push_back(v0); - _params.push_back(location); + (void)CHECK_GL_ERROR(); } void GLBackend::do_glUniform1i(Batch& batch, size_t paramOffset) { @@ -591,17 +405,9 @@ void GLBackend::do_glUniform1i(Batch& batch, size_t paramOffset) { glUniform1f( GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int), batch._params[paramOffset + 0]._int); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } -void Batch::_glUniform1f(GLint location, GLfloat v0) { - if (location < 0) { - return; - } - ADD_COMMAND_GL(glUniform1f); - _params.push_back(v0); - _params.push_back(location); -} 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 @@ -613,15 +419,7 @@ void GLBackend::do_glUniform1f(Batch& batch, size_t paramOffset) { glUniform1f( GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int), batch._params[paramOffset + 0]._float); - (void) CHECK_GL_ERROR(); -} - -void Batch::_glUniform2f(GLint location, GLfloat v0, GLfloat v1) { - ADD_COMMAND_GL(glUniform2f); - - _params.push_back(v1); - _params.push_back(v0); - _params.push_back(location); + (void)CHECK_GL_ERROR(); } void GLBackend::do_glUniform2f(Batch& batch, size_t paramOffset) { @@ -635,16 +433,7 @@ void GLBackend::do_glUniform2f(Batch& batch, size_t paramOffset) { GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int), batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float); - (void) CHECK_GL_ERROR(); -} - -void Batch::_glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2) { - ADD_COMMAND_GL(glUniform3f); - - _params.push_back(v2); - _params.push_back(v1); - _params.push_back(v0); - _params.push_back(location); + (void)CHECK_GL_ERROR(); } void GLBackend::do_glUniform3f(Batch& batch, size_t paramOffset) { @@ -659,21 +448,9 @@ void GLBackend::do_glUniform3f(Batch& batch, size_t paramOffset) { batch._params[paramOffset + 2]._float, batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } - -void Batch::_glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { - ADD_COMMAND_GL(glUniform4f); - - _params.push_back(v3); - _params.push_back(v2); - _params.push_back(v1); - _params.push_back(v0); - _params.push_back(location); -} - - 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 @@ -690,14 +467,6 @@ void GLBackend::do_glUniform4f(Batch& batch, size_t paramOffset) { (void)CHECK_GL_ERROR(); } -void Batch::_glUniform3fv(GLint location, GLsizei count, const GLfloat* value) { - ADD_COMMAND_GL(glUniform3fv); - - const int VEC3_SIZE = 3 * sizeof(float); - _params.push_back(cacheData(count * VEC3_SIZE, value)); - _params.push_back(count); - _params.push_back(location); -} 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 @@ -710,18 +479,9 @@ void GLBackend::do_glUniform3fv(Batch& batch, size_t paramOffset) { batch._params[paramOffset + 1]._uint, (const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint)); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } - -void Batch::_glUniform4fv(GLint location, GLsizei count, const GLfloat* value) { - ADD_COMMAND_GL(glUniform4fv); - - const int VEC4_SIZE = 4 * sizeof(float); - _params.push_back(cacheData(count * VEC4_SIZE, value)); - _params.push_back(count); - _params.push_back(location); -} 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 @@ -729,23 +489,15 @@ void GLBackend::do_glUniform4fv(Batch& batch, size_t paramOffset) { return; } updatePipeline(); - + GLint location = GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int); GLsizei count = batch._params[paramOffset + 1]._uint; const GLfloat* value = (const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint); glUniform4fv(location, count, value); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } -void Batch::_glUniform4iv(GLint location, GLsizei count, const GLint* value) { - ADD_COMMAND_GL(glUniform4iv); - - const int VEC4_SIZE = 4 * sizeof(int); - _params.push_back(cacheData(count * VEC4_SIZE, value)); - _params.push_back(count); - _params.push_back(location); -} 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 @@ -758,18 +510,25 @@ void GLBackend::do_glUniform4iv(Batch& batch, size_t paramOffset) { batch._params[paramOffset + 1]._uint, (const GLint*)batch.editData(batch._params[paramOffset + 0]._uint)); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } -void Batch::_glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) { - ADD_COMMAND_GL(glUniformMatrix4fv); +void GLBackend::do_glUniformMatrix3fv(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 + return; + } + updatePipeline(); - const int MATRIX4_SIZE = 16 * sizeof(float); - _params.push_back(cacheData(count * MATRIX4_SIZE, value)); - _params.push_back(transpose); - _params.push_back(count); - _params.push_back(location); + glUniformMatrix3fv( + GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int), + batch._params[paramOffset + 2]._uint, + batch._params[paramOffset + 1]._uint, + (const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint)); + (void)CHECK_GL_ERROR(); } + 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 @@ -783,42 +542,20 @@ void GLBackend::do_glUniformMatrix4fv(Batch& batch, size_t paramOffset) { batch._params[paramOffset + 2]._uint, batch._params[paramOffset + 1]._uint, (const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint)); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } -void Batch::_glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { - ADD_COMMAND_GL(glColor4f); - - _params.push_back(alpha); - _params.push_back(blue); - _params.push_back(green); - _params.push_back(red); -} void GLBackend::do_glColor4f(Batch& batch, size_t paramOffset) { glm::vec4 newColor( batch._params[paramOffset + 3]._float, batch._params[paramOffset + 2]._float, batch._params[paramOffset + 1]._float, - batch._params[paramOffset + 0]._float); + batch._params[paramOffset + 0]._float); if (_input._colorAttribute != newColor) { _input._colorAttribute = newColor; glVertexAttrib4fv(gpu::Stream::COLOR, &_input._colorAttribute.r); } - (void) CHECK_GL_ERROR(); -} - - -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, size_t paramOffset) { -#if defined(NSIGHT_FOUND) - nvtxRangePop(); -#endif + (void)CHECK_GL_ERROR(); } diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.h b/libraries/gpu-gl/src/gpu/gl/GLBackend.h index 95c079fc02..e39f9a1dff 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.h @@ -8,8 +8,8 @@ // 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_gpu_GLBackend_h -#define hifi_gpu_GLBackend_h +#ifndef hifi_gpu_gl_GLBackend_h +#define hifi_gpu_gl_GLBackend_h // Core 41 doesn't expose the features to really separate the vertex format from the vertex buffers binding // Core 43 does :) @@ -35,340 +35,37 @@ #include #include - -#define GPU_CORE 1 -#define GPU_LEGACY 0 -#define GPU_CORE_41 410 -#define GPU_CORE_43 430 -#define GPU_CORE_MINIMUM GPU_CORE_41 -#define GPU_FEATURE_PROFILE GPU_CORE - -#if defined(__APPLE__) - -#define GPU_INPUT_PROFILE GPU_CORE_41 - -#else - -#define GPU_INPUT_PROFILE GPU_CORE_43 - -#endif - - -#if (GPU_INPUT_PROFILE == GPU_CORE_43) -// Deactivate SSBO for now, we've run into some issues -// on GL 4.3 capable GPUs not behaving as expected -//#define GPU_SSBO_DRAW_CALL_INFO -#endif +#include "GLShared.h" namespace gpu { namespace gl { -class GLTextureTransferHelper; - class GLBackend : public Backend { - // Context Backend static interface required friend class gpu::Context; static void init(); static Backend* createBackend(); - static bool makeProgram(Shader& shader, const Shader::BindingSet& bindings); + static bool makeProgram(Shader& shader, const Shader::BindingSet& slotBindings); +protected: explicit GLBackend(bool syncCache); GLBackend(); public: - static Context::Size getDedicatedMemory(); + ~GLBackend(); - virtual ~GLBackend(); - - virtual void render(Batch& batch); + void render(Batch& batch) final; // This call synchronize the Full Backend cache with the current GLState // THis is only intended to be used when mixing raw gl calls with the gpu api usage in order to sync // the gpu::Backend state with the true gl state which has probably been messed up by these ugly naked gl calls // Let's try to avoid to do that as much as possible! - virtual void syncCache(); + void syncCache() final; // This is the ugly "download the pixels to sysmem for taking a snapshot" // Just avoid using it, it's ugly and will break performances - virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage); - - static void checkGLStackStable(std::function f); - - - - class GLBuffer : public GPUObject { - public: - const GLuint _buffer; - const GLuint _size; - const Stamp _stamp; - - GLBuffer(const Buffer& buffer, GLBuffer* original = nullptr); - ~GLBuffer(); - - virtual void transfer(); - - private: - bool getNextTransferBlock(GLintptr& outOffset, GLsizeiptr& outSize, size_t& currentPage) const; - - // The owning texture - const Buffer& _gpuBuffer; - }; - - static GLBuffer* syncGPUObject(const Buffer& buffer); - static GLuint getBufferID(const Buffer& buffer); - - class GLTexture : public GPUObject { - public: - // The public gl texture object - GLuint _texture{ 0 }; - - const Stamp _storageStamp; - Stamp _contentStamp { 0 }; - const GLenum _target; - const uint16 _maxMip; - const uint16 _minMip; - const bool _transferrable; - - struct DownsampleSource { - using Pointer = std::shared_ptr; - DownsampleSource(GLTexture& oldTexture); - ~DownsampleSource(); - const GLuint _texture; - const uint16 _minMip; - const uint16 _maxMip; - }; - - DownsampleSource::Pointer _downsampleSource; - - GLTexture(bool transferrable, const gpu::Texture& gpuTexture); - GLTexture(GLTexture& originalTexture, const gpu::Texture& gpuTexture); - ~GLTexture(); - - // Return a floating point value indicating how much of the allowed - // texture memory we are currently consuming. A value of 0 indicates - // no texture memory usage, while a value of 1 indicates all available / allowed memory - // is consumed. A value above 1 indicates that there is a problem. - static float getMemoryPressure(); - - void withPreservedTexture(std::function f); - - void createTexture(); - void allocateStorage(); - - GLuint size() const { return _size; } - GLuint virtualSize() const { return _virtualSize; } - - void updateSize(); - - enum SyncState { - // The texture is currently undergoing no processing, although it's content - // may be out of date, or it's storage may be invalid relative to the - // owning GPU texture - Idle, - // The texture has been queued for transfer to the GPU - Pending, - // The texture has been transferred to the GPU, but is awaiting - // any post transfer operations that may need to occur on the - // primary rendering thread - Transferred, - }; - - void setSyncState(SyncState syncState) { _syncState = syncState; } - SyncState getSyncState() const { return _syncState; } - - // Is the storage out of date relative to the gpu texture? - bool isInvalid() const; - - // Is the content out of date relative to the gpu texture? - bool isOutdated() const; - - // Is the texture in a state where it can be rendered with no work? - bool isReady() const; - - // Is this texture pushing us over the memory limit? - bool isOverMaxMemory() const; - - // Move the image bits from the CPU to the GPU - void transfer() const; - - // Execute any post-move operations that must occur only on the main thread - void postTransfer(); - - uint16 usedMipLevels() const { return (_maxMip - _minMip) + 1; } - - static const size_t CUBE_NUM_FACES = 6; - static const GLenum CUBE_FACE_LAYOUT[6]; - - private: - friend class GLTextureTransferHelper; - - GLTexture(bool transferrable, const gpu::Texture& gpuTexture, bool init); - // at creation the true texture is created in GL - // it becomes public only when ready. - GLuint _privateTexture{ 0 }; - - const std::vector& getFaceTargets() const; - - void setSize(GLuint size); - - const GLuint _virtualSize; // theorical size as expected - GLuint _size { 0 }; // true size as reported by the gl api - - void transferMip(uint16_t mipLevel, uint8_t face = 0) const; - - // The owning texture - const Texture& _gpuTexture; - std::atomic _syncState { SyncState::Idle }; - }; - static GLTexture* syncGPUObject(const TexturePointer& texture, bool needTransfer = true); - static GLuint getTextureID(const TexturePointer& texture, bool sync = true); - - // very specific for now - static void syncSampler(const Sampler& sampler, Texture::Type type, const GLTexture* object); - - class GLInputFormat : public GPUObject { - public: - std::string key; - - GLInputFormat(); - ~GLInputFormat(); - }; - GLInputFormat* syncGPUObject(const Stream::Format& inputFormat); - - class GLShader : public GPUObject { - public: - enum Version { - Mono = 0, - - NumVersions - }; - - struct ShaderObject { - GLuint glshader{ 0 }; - GLuint glprogram{ 0 }; - GLint transformCameraSlot{ -1 }; - GLint transformObjectSlot{ -1 }; - }; - - using ShaderObjects = std::array< ShaderObject, NumVersions >; - - using UniformMapping = std::map; - using UniformMappingVersions = std::vector; - - GLShader(); - ~GLShader(); - - ShaderObjects _shaderObjects; - UniformMappingVersions _uniformMappings; - - GLuint getProgram(Version version = Mono) const { - return _shaderObjects[version].glprogram; - } - - GLint getUniformLocation(GLint srcLoc, Version version = Mono) { - // THIS will be used in the future PR as we grow the number of versions - // return _uniformMappings[version][srcLoc]; - return srcLoc; - } - - }; - static GLShader* syncGPUObject(const Shader& shader); - - class GLState : public GPUObject { - public: - class Command { - public: - virtual void run(GLBackend* backend) = 0; - Command() {} - virtual ~Command() {}; - }; - - template class Command1 : public Command { - public: - typedef void (GLBackend::*GLFunction)(T); - void run(GLBackend* backend) { (backend->*(_func))(_param); } - Command1(GLFunction func, T param) : _func(func), _param(param) {}; - GLFunction _func; - T _param; - }; - template class Command2 : public Command { - public: - typedef void (GLBackend::*GLFunction)(T, U); - void run(GLBackend* backend) { (backend->*(_func))(_param0, _param1); } - Command2(GLFunction func, T param0, U param1) : _func(func), _param0(param0), _param1(param1) {}; - GLFunction _func; - T _param0; - U _param1; - }; - - template class Command3 : public Command { - public: - typedef void (GLBackend::*GLFunction)(T, U, V); - void run(GLBackend* backend) { (backend->*(_func))(_param0, _param1, _param2); } - Command3(GLFunction func, T param0, U param1, V param2) : _func(func), _param0(param0), _param1(param1), _param2(param2) {}; - GLFunction _func; - T _param0; - U _param1; - V _param2; - }; - - typedef std::shared_ptr< Command > CommandPointer; - typedef std::vector< CommandPointer > Commands; - - Commands _commands; - Stamp _stamp; - State::Signature _signature; - - GLState(); - ~GLState(); - - // The state commands to reset to default, - static const Commands _resetStateCommands; - - friend class GLBackend; - }; - static GLState* syncGPUObject(const State& state); - - class GLPipeline : public GPUObject { - public: - GLShader* _program = 0; - GLState* _state = 0; - - GLPipeline(); - ~GLPipeline(); - }; - static GLPipeline* syncGPUObject(const Pipeline& pipeline); + virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) final; - class GLFramebuffer : public GPUObject { - public: - GLuint _fbo = 0; - std::vector _colorBuffers; - Stamp _depthStamp { 0 }; - std::vector _colorStamps; - - - GLFramebuffer(); - ~GLFramebuffer(); - }; - static GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer); - static GLuint getFramebufferID(const FramebufferPointer& framebuffer); - - class GLQuery : public GPUObject { - public: - GLuint _qo = 0; - GLuint64 _result = 0; - - GLQuery(); - ~GLQuery(); - }; - static GLQuery* syncGPUObject(const Query& query); - static GLuint getQueryID(const QueryPointer& query); - - - static const int MAX_NUM_ATTRIBUTES = Stream::NUM_INPUT_SLOTS; - // The drawcall Info attribute channel is reserved and is the upper bound for the number of availables Input buffers - static const int MAX_NUM_INPUT_BUFFERS = Stream::DRAW_CALL_INFO; - + // this is the maximum numeber of available input buffers size_t getNumInputBuffers() const { return _input._invalidBuffers.size(); } // this is the maximum per shader stage on the low end apple @@ -381,70 +78,137 @@ public: static const int MAX_NUM_RESOURCE_TEXTURES = 16; size_t getMaxNumResourceTextures() const { return MAX_NUM_RESOURCE_TEXTURES; } + // Draw Stage + virtual void do_draw(Batch& batch, size_t paramOffset) = 0; + virtual void do_drawIndexed(Batch& batch, size_t paramOffset) = 0; + virtual void do_drawInstanced(Batch& batch, size_t paramOffset) = 0; + virtual void do_drawIndexedInstanced(Batch& batch, size_t paramOffset) = 0; + virtual void do_multiDrawIndirect(Batch& batch, size_t paramOffset) = 0; + virtual void do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) = 0; + + // Input Stage + virtual void do_setInputFormat(Batch& batch, size_t paramOffset) final; + virtual void do_setInputBuffer(Batch& batch, size_t paramOffset) final; + virtual void do_setIndexBuffer(Batch& batch, size_t paramOffset) final; + virtual void do_setIndirectBuffer(Batch& batch, size_t paramOffset) final; + virtual void do_generateTextureMips(Batch& batch, size_t paramOffset) final; + + // Transform Stage + virtual void do_setModelTransform(Batch& batch, size_t paramOffset) final; + virtual void do_setViewTransform(Batch& batch, size_t paramOffset) final; + virtual void do_setProjectionTransform(Batch& batch, size_t paramOffset) final; + virtual void do_setViewportTransform(Batch& batch, size_t paramOffset) final; + virtual void do_setDepthRangeTransform(Batch& batch, size_t paramOffset) final; + + // Uniform Stage + virtual void do_setUniformBuffer(Batch& batch, size_t paramOffset) final; + + // Resource Stage + virtual void do_setResourceTexture(Batch& batch, size_t paramOffset) final; + + // Pipeline Stage + virtual void do_setPipeline(Batch& batch, size_t paramOffset) final; + + // Output stage + virtual void do_setFramebuffer(Batch& batch, size_t paramOffset) final; + virtual void do_clearFramebuffer(Batch& batch, size_t paramOffset) final; + virtual void do_blit(Batch& batch, size_t paramOffset) = 0; + + // Query section + virtual void do_beginQuery(Batch& batch, size_t paramOffset) final; + virtual void do_endQuery(Batch& batch, size_t paramOffset) final; + virtual void do_getQuery(Batch& batch, size_t paramOffset) final; + + // Reset stages + virtual void do_resetStages(Batch& batch, size_t paramOffset) final; + + virtual void do_runLambda(Batch& batch, size_t paramOffset) final; + + virtual void do_startNamedCall(Batch& batch, size_t paramOffset) final; + virtual void do_stopNamedCall(Batch& batch, size_t paramOffset) final; + + static const int MAX_NUM_ATTRIBUTES = Stream::NUM_INPUT_SLOTS; + // The drawcall Info attribute channel is reserved and is the upper bound for the number of availables Input buffers + static const int MAX_NUM_INPUT_BUFFERS = Stream::DRAW_CALL_INFO; + + virtual void do_pushProfileRange(Batch& batch, size_t paramOffset) final; + virtual void do_popProfileRange(Batch& batch, size_t paramOffset) final; + + // 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 + virtual void do_glActiveBindTexture(Batch& batch, size_t paramOffset) final; + + virtual void do_glUniform1i(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform1f(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform2f(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform3f(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform4f(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform3fv(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform4fv(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform4iv(Batch& batch, size_t paramOffset) final; + virtual void do_glUniformMatrix3fv(Batch& batch, size_t paramOffset) final; + virtual void do_glUniformMatrix4fv(Batch& batch, size_t paramOffset) final; + + virtual void do_glColor4f(Batch& batch, size_t paramOffset) final; + // The State setters called by the GLState::Commands when a new state is assigned - void do_setStateFillMode(int32 mode); - void do_setStateCullMode(int32 mode); - void do_setStateFrontFaceClockwise(bool isClockwise); - void do_setStateDepthClampEnable(bool enable); - void do_setStateScissorEnable(bool enable); - void do_setStateMultisampleEnable(bool enable); - void do_setStateAntialiasedLineEnable(bool enable); + virtual void do_setStateFillMode(int32 mode) final; + virtual void do_setStateCullMode(int32 mode) final; + virtual void do_setStateFrontFaceClockwise(bool isClockwise) final; + virtual void do_setStateDepthClampEnable(bool enable) final; + virtual void do_setStateScissorEnable(bool enable) final; + virtual void do_setStateMultisampleEnable(bool enable) final; + virtual void do_setStateAntialiasedLineEnable(bool enable) final; + virtual void do_setStateDepthBias(Vec2 bias) final; + virtual void do_setStateDepthTest(State::DepthTest test) final; + virtual void do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest) final; + virtual void do_setStateAlphaToCoverageEnable(bool enable) final; + virtual void do_setStateSampleMask(uint32 mask) final; + virtual void do_setStateBlend(State::BlendFunction blendFunction) final; + virtual void do_setStateColorWriteMask(uint32 mask) final; + virtual void do_setStateBlendFactor(Batch& batch, size_t paramOffset) final; + virtual void do_setStateScissorRect(Batch& batch, size_t paramOffset) final; - void do_setStateDepthBias(Vec2 bias); - void do_setStateDepthTest(State::DepthTest test); - - void do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest); - - void do_setStateAlphaToCoverageEnable(bool enable); - void do_setStateSampleMask(uint32 mask); - - void do_setStateBlend(State::BlendFunction blendFunction); - - void do_setStateColorWriteMask(uint32 mask); - protected: - static const size_t INVALID_OFFSET = (size_t)-1; - bool _inRenderTransferPass; + virtual GLuint getFramebufferID(const FramebufferPointer& framebuffer) = 0; + virtual GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) = 0; + + virtual GLuint getBufferID(const Buffer& buffer) = 0; + virtual GLBuffer* syncGPUObject(const Buffer& buffer) = 0; + + virtual GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) = 0; + virtual GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) = 0; + + virtual GLuint getQueryID(const QueryPointer& query) = 0; + virtual GLQuery* syncGPUObject(const Query& query) = 0; + + static const size_t INVALID_OFFSET = (size_t)-1; + bool _inRenderTransferPass { false }; + int32_t _uboAlignment { 0 }; + int _currentDraw { -1 }; void renderPassTransfer(Batch& batch); void renderPassDraw(Batch& batch); - void setupStereoSide(int side); - void initTextureTransferHelper(); - static void transferGPUObject(const TexturePointer& texture); + virtual void initInput() final; + virtual void killInput() final; + virtual void syncInputStateCache() final; + virtual void resetInputStage() final; + virtual void updateInput(); - static std::shared_ptr _textureTransferHelper; - - // Draw Stage - 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, 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(); - void syncInputStateCache(); - void updateInput(); - void resetInputStage(); struct InputStageState { - bool _invalidFormat = true; + bool _invalidFormat { true }; Stream::FormatPointer _format; std::string _formatKey; typedef std::bitset ActivationCache; - ActivationCache _attributeActivation; + ActivationCache _attributeActivation { 0 }; typedef std::bitset BuffersState; + BuffersState _invalidBuffers{ 0 }; BuffersState _attribBindingBuffers{ 0 }; @@ -456,15 +220,15 @@ protected: glm::vec4 _colorAttribute{ 0.0f }; BufferPointer _indexBuffer; - Offset _indexBufferOffset; - Type _indexBufferType; + Offset _indexBufferOffset { 0 }; + Type _indexBufferType { UINT32 }; BufferPointer _indirectBuffer; Offset _indirectBufferOffset{ 0 }; Offset _indirectBufferStride{ 0 }; - GLuint _defaultVAO; - + GLuint _defaultVAO { 0 }; + InputStageState() : _invalidFormat(true), _format(0), @@ -473,22 +237,10 @@ protected: _buffers(_invalidBuffers.size(), BufferPointer(0)), _bufferOffsets(_invalidBuffers.size(), 0), _bufferStrides(_invalidBuffers.size(), 0), - _bufferVBOs(_invalidBuffers.size(), 0), - _indexBuffer(0), - _indexBufferOffset(0), - _indexBufferType(UINT32), - _defaultVAO(0) - {} + _bufferVBOs(_invalidBuffers.size(), 0) {} } _input; - // Transform Stage - 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(); + virtual void initTransform() = 0; void killTransform(); // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncTransformStateCache(); @@ -528,153 +280,74 @@ protected: void preUpdate(size_t commandIndex, const StereoState& stereo); void update(size_t commandIndex, const StereoState& stereo) const; void bindCurrentCamera(int stereoSide) const; - void transfer(const Batch& batch) const; } _transform; - int32_t _uboAlignment{ 0 }; + virtual void transferTransformState(const Batch& batch) const = 0; - - // Uniform Stage - void do_setUniformBuffer(Batch& batch, size_t paramOffset); + struct UniformStageState { + std::array _buffers; + //Buffers _buffers { }; + } _uniform; void releaseUniformBuffer(uint32_t slot); void resetUniformStage(); - struct UniformStageState { - Buffers _buffers; - - UniformStageState(): - _buffers(MAX_NUM_UNIFORM_BUFFERS, nullptr) - {} - } _uniform; - // Resource Stage - void do_setResourceTexture(Batch& batch, size_t paramOffset); - // update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s void releaseResourceTexture(uint32_t slot); void resetResourceStage(); + struct ResourceStageState { - Textures _textures; - + std::array _textures; + //Textures _textures { { MAX_NUM_RESOURCE_TEXTURES } }; int findEmptyTextureSlot() const; - - ResourceStageState(): - _textures(MAX_NUM_RESOURCE_TEXTURES, nullptr) - {} - } _resource; + size_t _commandIndex{ 0 }; - // Pipeline Stage - 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(); // Force to reset all the state fields indicated by the 'toBeReset" signature void resetPipelineState(State::Signature toBeReset); // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncPipelineStateCache(); - // Grab the actual gl state into it's gpu::State equivalent. THis is used by the above call syncPipleineStateCache() - void getCurrentGLState(State::Data& state); void resetPipelineStage(); struct PipelineStageState { - PipelinePointer _pipeline; - GLuint _program; - GLShader* _programShader; - bool _invalidProgram; + GLuint _program { 0 }; + GLShader* _programShader { nullptr }; + bool _invalidProgram { false }; - State::Data _stateCache; - State::Signature _stateSignatureCache; + State::Data _stateCache { State::DEFAULT }; + State::Signature _stateSignatureCache { 0 }; - GLState* _state; - bool _invalidState = false; - - PipelineStageState() : - _pipeline(), - _program(0), - _programShader(nullptr), - _invalidProgram(false), - _stateCache(State::DEFAULT), - _stateSignatureCache(0), - _state(nullptr), - _invalidState(false) - {} + GLState* _state { nullptr }; + bool _invalidState { false }; } _pipeline; - // Output stage - void do_setFramebuffer(Batch& batch, size_t paramOffset); - void do_clearFramebuffer(Batch& batch, size_t paramOffset); - void do_blit(Batch& batch, size_t paramOffset); - void do_generateTextureMips(Batch& batch, size_t paramOffset); - // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncOutputStateCache(); void resetOutputStage(); struct OutputStageState { - - FramebufferPointer _framebuffer = nullptr; - GLuint _drawFBO = 0; - - OutputStageState() {} + FramebufferPointer _framebuffer { nullptr }; + GLuint _drawFBO { 0 }; } _output; - // Query section - 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 { }; - // Reset stages - void do_resetStages(Batch& batch, size_t paramOffset); - - void do_runLambda(Batch& batch, size_t paramOffset); - - void do_startNamedCall(Batch& batch, size_t paramOffset); - void do_stopNamedCall(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, size_t 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, size_t paramOffset); - - void do_pushProfileRange(Batch& batch, size_t paramOffset); - void do_popProfileRange(Batch& batch, size_t paramOffset); - - int _currentDraw { -1 }; - typedef void (GLBackend::*CommandCall)(Batch&, size_t); static CommandCall _commandCalls[Batch::NUM_COMMANDS]; - + friend class GLState; }; } } -Q_DECLARE_LOGGING_CATEGORY(gpugllogging) - - #endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendBuffer.cpp deleted file mode 100644 index cab09477c1..0000000000 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendBuffer.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// -// GLBackendBuffer.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 3/8/2015. -// 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 "GLBackend.h" -#include "GLBackendShared.h" - -using namespace gpu; -using namespace gpu::gl; - -GLuint allocateSingleBuffer() { - GLuint result; - glGenBuffers(1, &result); - (void)CHECK_GL_ERROR(); - return result; -} - -GLBackend::GLBuffer::GLBuffer(const Buffer& buffer, GLBuffer* original) : - _buffer(allocateSingleBuffer()), - _size((GLuint)buffer._sysmem.getSize()), - _stamp(buffer._sysmem.getStamp()), - _gpuBuffer(buffer) { - glBindBuffer(GL_ARRAY_BUFFER, _buffer); - if (GLEW_VERSION_4_4 || GLEW_ARB_buffer_storage) { - glBufferStorage(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_STORAGE_BIT); - (void)CHECK_GL_ERROR(); - } else { - glBufferData(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_DRAW); - (void)CHECK_GL_ERROR(); - } - glBindBuffer(GL_ARRAY_BUFFER, 0); - - if (original && original->_size) { - glBindBuffer(GL_COPY_WRITE_BUFFER, _buffer); - glBindBuffer(GL_COPY_READ_BUFFER, original->_buffer); - glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, original->_size); - glBindBuffer(GL_COPY_WRITE_BUFFER, 0); - glBindBuffer(GL_COPY_READ_BUFFER, 0); - (void)CHECK_GL_ERROR(); - } - - Backend::setGPUObject(buffer, this); - Backend::incrementBufferGPUCount(); - Backend::updateBufferGPUMemoryUsage(0, _size); -} - -GLBackend::GLBuffer::~GLBuffer() { - if (_buffer != 0) { - glDeleteBuffers(1, &_buffer); - } - Backend::updateBufferGPUMemoryUsage(_size, 0); - Backend::decrementBufferGPUCount(); -} - -void GLBackend::GLBuffer::transfer() { - glBindBuffer(GL_ARRAY_BUFFER, _buffer); - (void)CHECK_GL_ERROR(); - GLintptr offset; - GLsizeiptr size; - size_t currentPage { 0 }; - auto data = _gpuBuffer.getSysmem().readData(); - while (getNextTransferBlock(offset, size, currentPage)) { - glBufferSubData(GL_ARRAY_BUFFER, offset, size, data + offset); - (void)CHECK_GL_ERROR(); - } - glBindBuffer(GL_ARRAY_BUFFER, 0); - (void)CHECK_GL_ERROR(); - _gpuBuffer._flags &= ~Buffer::DIRTY; -} - -bool GLBackend::GLBuffer::getNextTransferBlock(GLintptr& outOffset, GLsizeiptr& outSize, size_t& currentPage) const { - size_t pageCount = _gpuBuffer._pages.size(); - // Advance to the first dirty page - while (currentPage < pageCount && (0 == (Buffer::DIRTY & _gpuBuffer._pages[currentPage]))) { - ++currentPage; - } - - // If we got to the end, we're done - if (currentPage >= pageCount) { - return false; - } - - // Advance to the next clean page - outOffset = static_cast(currentPage * _gpuBuffer._pageSize); - while (currentPage < pageCount && (0 != (Buffer::DIRTY & _gpuBuffer._pages[currentPage]))) { - _gpuBuffer._pages[currentPage] &= ~Buffer::DIRTY; - ++currentPage; - } - outSize = static_cast((currentPage * _gpuBuffer._pageSize) - outOffset); - return true; -} - -GLBackend::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) { - GLBuffer* object = Backend::getGPUObject(buffer); - - // Has the storage size changed? - if (!object || object->_stamp != buffer.getSysmem().getStamp()) { - object = new GLBuffer(buffer, object); - } - - if (0 != (buffer._flags & Buffer::DIRTY)) { - object->transfer(); - } - - return object; -} - - - -GLuint GLBackend::getBufferID(const Buffer& buffer) { - GLBuffer* bo = GLBackend::syncGPUObject(buffer); - if (bo) { - return bo->_buffer; - } else { - return 0; - } -} - diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp index 3ae38c095f..d390a1e29d 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp @@ -9,45 +9,26 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" -#include "GLBackendShared.h" +#include "GLShared.h" +#include "GLInputFormat.h" using namespace gpu; using namespace gpu::gl; - -GLBackend::GLInputFormat::GLInputFormat() { -} - -GLBackend::GLInputFormat:: ~GLInputFormat() { - -} - -GLBackend::GLInputFormat* GLBackend::syncGPUObject(const Stream::Format& inputFormat) { - GLInputFormat* object = Backend::getGPUObject(inputFormat); - - if (!object) { - object = new GLInputFormat(); - object->key = inputFormat.getKey(); - Backend::setGPUObject(inputFormat, object); - } - - return object; -} - void GLBackend::do_setInputFormat(Batch& batch, size_t paramOffset) { Stream::FormatPointer format = batch._streamFormats.get(batch._params[paramOffset]._uint); - if(GLBackend::syncGPUObject(*format)) { - if (format != _input._format) { - _input._format = format; - if (format) { - if (_input._formatKey != format->getKey()) { - _input._formatKey = format->getKey(); - _input._invalidFormat = true; - } - } else { - _input._formatKey.clear(); + if (format != _input._format) { + _input._format = format; + if (format) { + auto inputFormat = GLInputFormat::sync((*format)); + assert(inputFormat); + if (_input._formatKey != inputFormat->key) { + _input._formatKey = inputFormat->key; _input._invalidFormat = true; } + } else { + _input._formatKey.clear(); + _input._invalidFormat = true; } } } @@ -114,6 +95,7 @@ void GLBackend::syncInputStateCache() { glBindVertexArray(_input._defaultVAO); } + void GLBackend::updateInput() { #if defined(SUPPORT_VERTEX_ATTRIB_FORMAT) if (_input._invalidFormat) { @@ -122,6 +104,7 @@ void GLBackend::updateInput() { // Assign the vertex format required if (_input._format) { +<<<<<<< HEAD _input._attribBindingBuffers.reset(); const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); @@ -163,9 +146,32 @@ void GLBackend::updateInput() { } (void)CHECK_GL_ERROR(); +======= + for (auto& it : _input._format->getAttributes()) { + const Stream::Attribute& attrib = (it).second; + + GLuint slot = attrib._slot; + GLuint count = attrib._element.getLocationScalarCount(); + uint8_t locationCount = attrib._element.getLocationCount(); + GLenum type = _elementTypeToGL41Type[attrib._element.getType()]; + GLuint offset = attrib._offset;; + GLboolean isNormalized = attrib._element.isNormalized(); + + GLenum perLocationSize = attrib._element.getLocationSize(); + + for (size_t locNum = 0; locNum < locationCount; ++locNum) { + newActivation.set(slot + locNum); + glVertexAttribFormat(slot + locNum, count, type, isNormalized, offset + locNum * perLocationSize); + glVertexAttribBinding(slot + locNum, attrib._channel); +>>>>>>> 592a50356bf598a4ca49d45c0c30525477831c6d } glVertexBindingDivisor(bufferChannelNum, frequency); } +<<<<<<< HEAD +======= + (void)CHECK_GL_ERROR(); + } +>>>>>>> 592a50356bf598a4ca49d45c0c30525477831c6d // Manage Activation what was and what is expected now // This should only disable VertexAttribs since the one in use have been disabled above @@ -182,7 +188,11 @@ void GLBackend::updateInput() { } (void)CHECK_GL_ERROR(); } +<<<<<<< HEAD +======= + (void)CHECK_GL_ERROR(); +>>>>>>> 592a50356bf598a4ca49d45c0c30525477831c6d _input._invalidFormat = false; _stats._ISNumFormatChanges++; @@ -200,7 +210,7 @@ void GLBackend::updateInput() { } _input._invalidBuffers.reset(); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } #else if (_input._invalidFormat || _input._invalidBuffers.any()) { @@ -220,7 +230,7 @@ void GLBackend::updateInput() { } } } - + // Manage Activation what was and what is expected now for (unsigned int i = 0; i < newActivation.size(); i++) { bool newState = newActivation[i]; @@ -231,8 +241,8 @@ void GLBackend::updateInput() { } else { glDisableVertexAttribArray(i); } - (void) CHECK_GL_ERROR(); - + (void)CHECK_GL_ERROR(); + _input._attributeActivation.flip(i); } } @@ -255,11 +265,11 @@ void GLBackend::updateInput() { int bufferNum = (channelIt).first; if (_input._invalidBuffers.test(bufferNum) || _input._invalidFormat) { - // GLuint vbo = gpu::GLBackend::getBufferID((*buffers[bufferNum])); + // GLuint vbo = gpu::GL41Backend::getBufferID((*buffers[bufferNum])); GLuint vbo = _input._bufferVBOs[bufferNum]; if (boundVBO != vbo) { glBindBuffer(GL_ARRAY_BUFFER, vbo); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); boundVBO = vbo; } _input._invalidBuffers[bufferNum] = false; @@ -269,7 +279,7 @@ void GLBackend::updateInput() { GLuint slot = attrib._slot; GLuint count = attrib._element.getLocationScalarCount(); uint8_t locationCount = attrib._element.getLocationCount(); - GLenum type = _elementTypeToGLType[attrib._element.getType()]; + GLenum type = gl::ELEMENT_TYPE_TO_GL[attrib._element.getType()]; // GLenum perLocationStride = strides[bufferNum]; GLenum perLocationStride = attrib._element.getLocationSize(); GLuint stride = (GLuint)strides[bufferNum]; @@ -281,10 +291,10 @@ void GLBackend::updateInput() { reinterpret_cast(pointer + perLocationStride * (GLuint)locNum)); glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency); } - + // TODO: Support properly the IAttrib version - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } } } @@ -296,6 +306,17 @@ void GLBackend::updateInput() { #endif } +// Core 41 doesn't expose the features to really separate the vertex format from the vertex buffers binding +// Core 43 does :) +// FIXME crashing problem with glVertexBindingDivisor / glVertexAttribFormat +// Once resolved, break this up into the GL 4.1 and 4.5 backends +#if 1 || (GPU_INPUT_PROFILE == GPU_CORE_41) +#define NO_SUPPORT_VERTEX_ATTRIB_FORMAT +#else +#define SUPPORT_VERTEX_ATTRIB_FORMAT +#endif + + void GLBackend::resetInputStage() { // Reset index buffer _input._indexBufferType = UINT32; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp index f0a29a19ba..1d46078b5b 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp @@ -9,180 +9,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" +#include "GLShared.h" +#include "GLFramebuffer.h" #include -#include "GLBackendShared.h" - using namespace gpu; using namespace gpu::gl; -GLBackend::GLFramebuffer::GLFramebuffer() {} - -GLBackend::GLFramebuffer::~GLFramebuffer() { - if (_fbo != 0) { - glDeleteFramebuffers(1, &_fbo); - } -} - -GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffer) { - GLFramebuffer* object = Backend::getGPUObject(framebuffer); - - bool needsUpate { false }; - if (!object || - framebuffer.getDepthStamp() != object->_depthStamp || - framebuffer.getColorStamps() != object->_colorStamps) { - needsUpate = true; - } - - // If GPU object already created and in sync - if (!needsUpate) { - return object; - } else if (framebuffer.isEmpty()) { - // NO framebuffer definition yet so let's avoid thinking - return nullptr; - } - - // need to have a gpu object? - if (!object) { - // All is green, assign the gpuobject to the Framebuffer - object = new GLFramebuffer(); - Backend::setGPUObject(framebuffer, object); - glGenFramebuffers(1, &object->_fbo); - (void)CHECK_GL_ERROR(); - } - - if (needsUpate) { - GLint currentFBO = -1; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); - glBindFramebuffer(GL_FRAMEBUFFER, object->_fbo); - - GLTexture* gltexture = nullptr; - TexturePointer surface; - if (framebuffer.getColorStamps() != object->_colorStamps) { - if (framebuffer.hasColor()) { - object->_colorBuffers.clear(); - static const GLenum colorAttachments[] = { - GL_COLOR_ATTACHMENT0, - GL_COLOR_ATTACHMENT1, - GL_COLOR_ATTACHMENT2, - GL_COLOR_ATTACHMENT3, - GL_COLOR_ATTACHMENT4, - GL_COLOR_ATTACHMENT5, - GL_COLOR_ATTACHMENT6, - GL_COLOR_ATTACHMENT7, - GL_COLOR_ATTACHMENT8, - GL_COLOR_ATTACHMENT9, - GL_COLOR_ATTACHMENT10, - GL_COLOR_ATTACHMENT11, - GL_COLOR_ATTACHMENT12, - GL_COLOR_ATTACHMENT13, - GL_COLOR_ATTACHMENT14, - GL_COLOR_ATTACHMENT15 }; - - int unit = 0; - for (auto& b : framebuffer.getRenderBuffers()) { - surface = b._texture; - if (surface) { - gltexture = GLBackend::syncGPUObject(surface, false); // Grab the gltexture and don't transfer - } else { - gltexture = nullptr; - } - - if (gltexture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); - object->_colorBuffers.push_back(colorAttachments[unit]); - } else { - glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, 0, 0); - } - unit++; - } - } - object->_colorStamps = framebuffer.getColorStamps(); - } - - GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT; - if (!framebuffer.hasStencil()) { - attachement = GL_DEPTH_ATTACHMENT; - } else if (!framebuffer.hasDepth()) { - attachement = GL_STENCIL_ATTACHMENT; - } - - if (framebuffer.getDepthStamp() != object->_depthStamp) { - auto surface = framebuffer.getDepthStencilBuffer(); - if (framebuffer.hasDepthStencil() && surface) { - gltexture = GLBackend::syncGPUObject(surface, false); // Grab the gltexture and don't transfer - } - - if (gltexture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); - } else { - glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, 0, 0); - } - object->_depthStamp = framebuffer.getDepthStamp(); - } - - - // Last but not least, define where we draw - if (!object->_colorBuffers.empty()) { - glDrawBuffers((GLsizei)object->_colorBuffers.size(), object->_colorBuffers.data()); - } else { - glDrawBuffer( GL_NONE ); - } - - // Now check for completness - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - // restore the current framebuffer - if (currentFBO != -1) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO); - } - - bool result = false; - switch (status) { - case GL_FRAMEBUFFER_COMPLETE : - // Success ! - result = true; - break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT : - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; - break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; - break; - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER : - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; - break; - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER : - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; - break; - case GL_FRAMEBUFFER_UNSUPPORTED : - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; - break; - } - if (!result && object->_fbo) { - glDeleteFramebuffers(1, &object->_fbo); - return nullptr; - } - } - - return object; -} - - - -GLuint GLBackend::getFramebufferID(const FramebufferPointer& framebuffer) { - if (!framebuffer) { - return 0; - } - GLFramebuffer* object = GLBackend::syncGPUObject(*framebuffer); - if (object) { - return object->_fbo; - } else { - return 0; - } -} - void GLBackend::syncOutputStateCache() { GLint currentFBO; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); @@ -303,44 +137,6 @@ void GLBackend::do_clearFramebuffer(Batch& batch, size_t paramOffset) { (void) CHECK_GL_ERROR(); } -void GLBackend::do_blit(Batch& batch, size_t paramOffset) { - auto srcframebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); - Vec4i srcvp; - for (auto i = 0; i < 4; ++i) { - srcvp[i] = batch._params[paramOffset + 1 + i]._int; - } - - auto dstframebuffer = batch._framebuffers.get(batch._params[paramOffset + 5]._uint); - Vec4i dstvp; - for (auto i = 0; i < 4; ++i) { - dstvp[i] = batch._params[paramOffset + 6 + i]._int; - } - - // Assign dest framebuffer if not bound already - auto newDrawFBO = getFramebufferID(dstframebuffer); - if (_output._drawFBO != newDrawFBO) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, newDrawFBO); - } - - // always bind the read fbo - glBindFramebuffer(GL_READ_FRAMEBUFFER, getFramebufferID(srcframebuffer)); - - // Blit! - glBlitFramebuffer(srcvp.x, srcvp.y, srcvp.z, srcvp.w, - dstvp.x, dstvp.y, dstvp.z, dstvp.w, - GL_COLOR_BUFFER_BIT, GL_LINEAR); - - // Always clean the read fbo to 0 - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - // Restore draw fbo if changed - if (_output._drawFBO != newDrawFBO) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _output._drawFBO); - } - - (void) CHECK_GL_ERROR(); -} - void GLBackend::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) { auto readFBO = getFramebufferID(srcFramebuffer); if (srcFramebuffer && readFBO) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp index b66b672850..04f56ba0f5 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp @@ -9,54 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" -#include "GLBackendShared.h" +#include "GLShared.h" +#include "GLPipeline.h" +#include "GLShader.h" +#include "GLState.h" +#include "GLBuffer.h" +#include "GLTexture.h" using namespace gpu; using namespace gpu::gl; -GLBackend::GLPipeline::GLPipeline() : - _program(nullptr), - _state(nullptr) -{} - -GLBackend::GLPipeline::~GLPipeline() { - _program = nullptr; - _state = nullptr; -} - -GLBackend::GLPipeline* GLBackend::syncGPUObject(const Pipeline& pipeline) { - GLPipeline* object = Backend::getGPUObject(pipeline); - - // If GPU object already created then good - if (object) { - return object; - } - - // No object allocated yet, let's see if it's worth it... - ShaderPointer shader = pipeline.getProgram(); - GLShader* programObject = GLBackend::syncGPUObject((*shader)); - if (programObject == nullptr) { - return nullptr; - } - - StatePointer state = pipeline.getState(); - GLState* stateObject = GLBackend::syncGPUObject((*state)); - if (stateObject == nullptr) { - return nullptr; - } - - // Program and state are valid, we can create the pipeline object - if (!object) { - object = new GLPipeline(); - Backend::setGPUObject(pipeline, object); - } - - object->_program = programObject; - object->_state = stateObject; - - return object; -} - void GLBackend::do_setPipeline(Batch& batch, size_t paramOffset) { PipelinePointer pipeline = batch._pipelines.get(batch._params[paramOffset + 0]._uint); @@ -78,7 +40,7 @@ void GLBackend::do_setPipeline(Batch& batch, size_t paramOffset) { _pipeline._state = nullptr; _pipeline._invalidState = true; } else { - auto pipelineObject = syncGPUObject((*pipeline)); + auto pipelineObject = GLPipeline::sync(*pipeline); if (!pipelineObject) { return; } @@ -155,14 +117,12 @@ void GLBackend::resetPipelineStage() { glUseProgram(0); } - void GLBackend::releaseUniformBuffer(uint32_t slot) { auto& buf = _uniform._buffers[slot]; if (buf) { - auto* object = Backend::getGPUObject(*buf); + auto* object = Backend::getGPUObject(*buf); if (object) { glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE - (void) CHECK_GL_ERROR(); } buf.reset(); @@ -181,9 +141,6 @@ void GLBackend::do_setUniformBuffer(Batch& batch, size_t paramOffset) { GLintptr rangeStart = batch._params[paramOffset + 1]._uint; GLsizeiptr rangeSize = batch._params[paramOffset + 0]._uint; - - - if (!uniformBuffer) { releaseUniformBuffer(slot); return; @@ -195,7 +152,7 @@ void GLBackend::do_setUniformBuffer(Batch& batch, size_t paramOffset) { } // Sync BufferObject - auto* object = GLBackend::syncGPUObject(*uniformBuffer); + auto* object = syncGPUObject(*uniformBuffer); if (object) { glBindBufferRange(GL_UNIFORM_BUFFER, slot, object->_buffer, rangeStart, rangeSize); @@ -210,19 +167,17 @@ void GLBackend::do_setUniformBuffer(Batch& batch, size_t paramOffset) { void GLBackend::releaseResourceTexture(uint32_t slot) { auto& tex = _resource._textures[slot]; if (tex) { - auto* object = Backend::getGPUObject(*tex); + auto* object = Backend::getGPUObject(*tex); if (object) { GLuint target = object->_target; glActiveTexture(GL_TEXTURE0 + slot); glBindTexture(target, 0); // RELEASE - (void) CHECK_GL_ERROR(); } tex.reset(); } } - void GLBackend::resetResourceStage() { for (uint32_t i = 0; i < _resource._textures.size(); i++) { releaseResourceTexture(i); @@ -246,7 +201,7 @@ void GLBackend::do_setResourceTexture(Batch& batch, size_t paramOffset) { _stats._RSNumTextureBounded++; // Always make sure the GLObject is in sync - GLTexture* object = GLBackend::syncGPUObject(resourceTexture); + GLTexture* object = syncGPUObject(resourceTexture); if (object) { GLuint to = object->_texture; GLuint target = object->_target; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp index 4f5b2ff1bf..463cff9a6c 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp @@ -9,63 +9,27 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" -#include "GLBackendShared.h" +#include "GLQuery.h" using namespace gpu; using namespace gpu::gl; -GLBackend::GLQuery::GLQuery() {} - -GLBackend::GLQuery::~GLQuery() { - if (_qo != 0) { - glDeleteQueries(1, &_qo); - } -} - -GLBackend::GLQuery* GLBackend::syncGPUObject(const Query& query) { - GLQuery* object = Backend::getGPUObject(query); - - // If GPU object already created and in sync - if (object) { - return object; - } - - // need to have a gpu object? - if (!object) { - GLuint qo; - glGenQueries(1, &qo); - (void) CHECK_GL_ERROR(); - GLuint64 result = -1; - - // All is green, assign the gpuobject to the Query - object = new GLQuery(); - object->_qo = qo; - object->_result = result; - Backend::setGPUObject(query, object); - } - - return object; -} - - - -GLuint GLBackend::getQueryID(const QueryPointer& query) { - if (!query) { - return 0; - } - GLQuery* object = GLBackend::syncGPUObject(*query); - if (object) { - return object->_qo; - } else { - return 0; - } -} +// Eventually, we want to test with TIME_ELAPSED instead of TIMESTAMP +#ifdef Q_OS_MAC +static bool timeElapsed = true; +#else +static bool timeElapsed = false; +#endif void GLBackend::do_beginQuery(Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { - glBeginQuery(GL_TIME_ELAPSED, glquery->_qo); + if (timeElapsed) { + glBeginQuery(GL_TIME_ELAPSED, glquery->_endqo); + } else { + glQueryCounter(glquery->_beginqo, GL_TIMESTAMP); + } (void)CHECK_GL_ERROR(); } } @@ -74,7 +38,11 @@ void GLBackend::do_endQuery(Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { - glEndQuery(GL_TIME_ELAPSED); + if (timeElapsed) { + glEndQuery(GL_TIME_ELAPSED); + } else { + glQueryCounter(glquery->_endqo, GL_TIMESTAMP); + } (void)CHECK_GL_ERROR(); } } @@ -83,9 +51,16 @@ void GLBackend::do_getQuery(Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { - glGetQueryObjectui64v(glquery->_qo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result); + glGetQueryObjectui64v(glquery->_endqo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result); if (glquery->_result == GL_TRUE) { - glGetQueryObjectui64v(glquery->_qo, GL_QUERY_RESULT, &glquery->_result); + if (timeElapsed) { + glGetQueryObjectui64v(glquery->_endqo, GL_QUERY_RESULT, &glquery->_result); + } else { + GLuint64 start, end; + glGetQueryObjectui64v(glquery->_beginqo, GL_QUERY_RESULT, &start); + glGetQueryObjectui64v(glquery->_endqo, GL_QUERY_RESULT, &end); + glquery->_result = end - start; + } query->triggerReturnHandler(glquery->_result); } (void)CHECK_GL_ERROR(); @@ -94,4 +69,3 @@ void GLBackend::do_getQuery(Batch& batch, size_t paramOffset) { void GLBackend::resetQueryStage() { } - diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendShared.h b/libraries/gpu-gl/src/gpu/gl/GLBackendShared.h deleted file mode 100644 index 0f7215f364..0000000000 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendShared.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// GLBackendShared.h -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 1/22/2014. -// 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_gpu_GLBackend_Shared_h -#define hifi_gpu_GLBackend_Shared_h - -#include -#include -#include - -namespace gpu { namespace gl { - -static const GLenum _primitiveToGLmode[gpu::NUM_PRIMITIVES] = { - GL_POINTS, - GL_LINES, - GL_LINE_STRIP, - GL_TRIANGLES, - GL_TRIANGLE_STRIP, - GL_TRIANGLE_FAN, -}; - -static const GLenum _elementTypeToGLType[gpu::NUM_TYPES] = { - GL_FLOAT, - GL_INT, - GL_UNSIGNED_INT, - GL_HALF_FLOAT, - GL_SHORT, - GL_UNSIGNED_SHORT, - GL_BYTE, - GL_UNSIGNED_BYTE, - // Normalized values - GL_INT, - GL_UNSIGNED_INT, - GL_SHORT, - GL_UNSIGNED_SHORT, - GL_BYTE, - GL_UNSIGNED_BYTE -}; - -class GLTexelFormat { -public: - GLenum internalFormat; - GLenum format; - GLenum type; - - static GLTexelFormat evalGLTexelFormat(const gpu::Element& dstFormat) { - return evalGLTexelFormat(dstFormat, dstFormat); - } - static GLTexelFormat evalGLTexelFormatInternal(const gpu::Element& dstFormat); - - static GLTexelFormat evalGLTexelFormat(const gpu::Element& dstFormat, const gpu::Element& srcFormat); -}; - -bool checkGLError(const char* name = nullptr); -bool checkGLErrorDebug(const char* name = nullptr); - -} } - -#define CHECK_GL_ERROR() gpu::gl::checkGLErrorDebug(__FUNCTION__) - -#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp index 1c1e0b38a4..a42b0dca6f 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp @@ -9,238 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" -#include "GLBackendShared.h" +#include "GLState.h" using namespace gpu; using namespace gpu::gl; -GLBackend::GLState::GLState() -{} - -GLBackend::GLState::~GLState() { -} - - -typedef GLBackend::GLState::Command Command; -typedef GLBackend::GLState::CommandPointer CommandPointer; -typedef GLBackend::GLState::Command1 Command1U; -typedef GLBackend::GLState::Command1 Command1I; -typedef GLBackend::GLState::Command1 Command1B; -typedef GLBackend::GLState::Command1 CommandDepthBias; -typedef GLBackend::GLState::Command1 CommandDepthTest; -typedef GLBackend::GLState::Command3 CommandStencil; -typedef GLBackend::GLState::Command1 CommandBlend; - -const GLBackend::GLState::Commands makeResetStateCommands(); -const GLBackend::GLState::Commands GLBackend::GLState::_resetStateCommands = makeResetStateCommands(); - - -// NOTE: This must stay in sync with the ordering of the State::Field enum -const GLBackend::GLState::Commands makeResetStateCommands() { - // Since State::DEFAULT is a static defined in another .cpp the initialisation order is random - // and we have a 50/50 chance that State::DEFAULT is not yet initialized. - // Since State::DEFAULT = State::Data() it is much easier to not use the actual State::DEFAULT - // but another State::Data object with a default initialization. - const State::Data DEFAULT = State::Data(); - - auto depthBiasCommand = std::make_shared(&GLBackend::do_setStateDepthBias, - Vec2(DEFAULT.depthBias, DEFAULT.depthBiasSlopeScale)); - auto stencilCommand = std::make_shared(&GLBackend::do_setStateStencil, DEFAULT.stencilActivation, - DEFAULT.stencilTestFront, DEFAULT.stencilTestBack); - - // The state commands to reset to default, - // WARNING depending on the order of the State::Field enum - return { - std::make_shared(&GLBackend::do_setStateFillMode, DEFAULT.fillMode), - std::make_shared(&GLBackend::do_setStateCullMode, DEFAULT.cullMode), - std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, DEFAULT.frontFaceClockwise), - std::make_shared(&GLBackend::do_setStateDepthClampEnable, DEFAULT.depthClampEnable), - std::make_shared(&GLBackend::do_setStateScissorEnable, DEFAULT.scissorEnable), - std::make_shared(&GLBackend::do_setStateMultisampleEnable, DEFAULT.multisampleEnable), - std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, DEFAULT.antialisedLineEnable), - - // Depth bias has 2 fields in State but really one call in GLBackend - CommandPointer(depthBiasCommand), - CommandPointer(depthBiasCommand), - - std::make_shared(&GLBackend::do_setStateDepthTest, DEFAULT.depthTest), - - // Depth bias has 3 fields in State but really one call in GLBackend - CommandPointer(stencilCommand), - CommandPointer(stencilCommand), - CommandPointer(stencilCommand), - - std::make_shared(&GLBackend::do_setStateSampleMask, DEFAULT.sampleMask), - - std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable), - - std::make_shared(&GLBackend::do_setStateBlend, DEFAULT.blendFunction), - - std::make_shared(&GLBackend::do_setStateColorWriteMask, DEFAULT.colorWriteMask) - }; -} - -void generateFillMode(GLBackend::GLState::Commands& commands, State::FillMode fillMode) { - commands.push_back(std::make_shared(&GLBackend::do_setStateFillMode, int32(fillMode))); -} - -void generateCullMode(GLBackend::GLState::Commands& commands, State::CullMode cullMode) { - commands.push_back(std::make_shared(&GLBackend::do_setStateCullMode, int32(cullMode))); -} - -void generateFrontFaceClockwise(GLBackend::GLState::Commands& commands, bool isClockwise) { - commands.push_back(std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, isClockwise)); -} - -void generateDepthClampEnable(GLBackend::GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateDepthClampEnable, enable)); -} - -void generateScissorEnable(GLBackend::GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateScissorEnable, enable)); -} - -void generateMultisampleEnable(GLBackend::GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateMultisampleEnable, enable)); -} - -void generateAntialiasedLineEnable(GLBackend::GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, enable)); -} - -void generateDepthBias(GLBackend::GLState::Commands& commands, const State& state) { - commands.push_back(std::make_shared(&GLBackend::do_setStateDepthBias, Vec2(state.getDepthBias(), state.getDepthBiasSlopeScale()))); -} - -void generateDepthTest(GLBackend::GLState::Commands& commands, const State::DepthTest& test) { - commands.push_back(std::make_shared(&GLBackend::do_setStateDepthTest, int32(test.getRaw()))); -} - -void generateStencil(GLBackend::GLState::Commands& commands, const State& state) { - commands.push_back(std::make_shared(&GLBackend::do_setStateStencil, state.getStencilActivation(), state.getStencilTestFront(), state.getStencilTestBack())); -} - -void generateAlphaToCoverageEnable(GLBackend::GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, enable)); -} - -void generateSampleMask(GLBackend::GLState::Commands& commands, uint32 mask) { - commands.push_back(std::make_shared(&GLBackend::do_setStateSampleMask, mask)); -} - -void generateBlend(GLBackend::GLState::Commands& commands, const State& state) { - commands.push_back(std::make_shared(&GLBackend::do_setStateBlend, state.getBlendFunction())); -} - -void generateColorWriteMask(GLBackend::GLState::Commands& commands, uint32 mask) { - commands.push_back(std::make_shared(&GLBackend::do_setStateColorWriteMask, mask)); -} - -GLBackend::GLState* GLBackend::syncGPUObject(const State& state) { - GLState* object = Backend::getGPUObject(state); - - // If GPU object already created then good - if (object) { - return object; - } - - // Else allocate and create the GLState - if (!object) { - object = new GLState(); - Backend::setGPUObject(state, object); - } - - // here, we need to regenerate something so let's do it all - object->_commands.clear(); - object->_stamp = state.getStamp(); - object->_signature = state.getSignature(); - - bool depthBias = false; - bool stencilState = false; - - // go thorugh the list of state fields in the State and record the corresponding gl command - for (int i = 0; i < State::NUM_FIELDS; i++) { - if (state.getSignature()[i]) { - switch(i) { - case State::FILL_MODE: { - generateFillMode(object->_commands, state.getFillMode()); - break; - } - case State::CULL_MODE: { - generateCullMode(object->_commands, state.getCullMode()); - break; - } - case State::DEPTH_BIAS: - case State::DEPTH_BIAS_SLOPE_SCALE: { - depthBias = true; - break; - } - case State::FRONT_FACE_CLOCKWISE: { - generateFrontFaceClockwise(object->_commands, state.isFrontFaceClockwise()); - break; - } - case State::DEPTH_CLAMP_ENABLE: { - generateDepthClampEnable(object->_commands, state.isDepthClampEnable()); - break; - } - case State::SCISSOR_ENABLE: { - generateScissorEnable(object->_commands, state.isScissorEnable()); - break; - } - case State::MULTISAMPLE_ENABLE: { - generateMultisampleEnable(object->_commands, state.isMultisampleEnable()); - break; - } - case State::ANTIALISED_LINE_ENABLE: { - generateAntialiasedLineEnable(object->_commands, state.isAntialiasedLineEnable()); - break; - } - case State::DEPTH_TEST: { - generateDepthTest(object->_commands, state.getDepthTest()); - break; - } - - case State::STENCIL_ACTIVATION: - case State::STENCIL_TEST_FRONT: - case State::STENCIL_TEST_BACK: { - stencilState = true; - break; - } - - case State::SAMPLE_MASK: { - generateSampleMask(object->_commands, state.getSampleMask()); - break; - } - case State::ALPHA_TO_COVERAGE_ENABLE: { - generateAlphaToCoverageEnable(object->_commands, state.isAlphaToCoverageEnabled()); - break; - } - - case State::BLEND_FUNCTION: { - generateBlend(object->_commands, state); - break; - } - - case State::COLOR_WRITE_MASK: { - generateColorWriteMask(object->_commands, state.getColorWriteMask()); - break; - } - } - } - } - - if (depthBias) { - generateDepthBias(object->_commands, state); - } - - if (stencilState) { - generateStencil(object->_commands, state); - } - - return object; -} - - void GLBackend::resetPipelineState(State::Signature nextSignature) { auto currentNotSignature = ~_pipeline._stateSignatureCache; auto nextNotSignature = ~nextSignature; @@ -255,230 +28,6 @@ void GLBackend::resetPipelineState(State::Signature nextSignature) { } } -ComparisonFunction comparisonFuncFromGL(GLenum func) { - if (func == GL_NEVER) { - return NEVER; - } else if (func == GL_LESS) { - return LESS; - } else if (func == GL_EQUAL) { - return EQUAL; - } else if (func == GL_LEQUAL) { - return LESS_EQUAL; - } else if (func == GL_GREATER) { - return GREATER; - } else if (func == GL_NOTEQUAL) { - return NOT_EQUAL; - } else if (func == GL_GEQUAL) { - return GREATER_EQUAL; - } else if (func == GL_ALWAYS) { - return ALWAYS; - } - - return ALWAYS; -} - -State::StencilOp stencilOpFromGL(GLenum stencilOp) { - if (stencilOp == GL_KEEP) { - return State::STENCIL_OP_KEEP; - } else if (stencilOp == GL_ZERO) { - return State::STENCIL_OP_ZERO; - } else if (stencilOp == GL_REPLACE) { - return State::STENCIL_OP_REPLACE; - } else if (stencilOp == GL_INCR_WRAP) { - return State::STENCIL_OP_INCR_SAT; - } else if (stencilOp == GL_DECR_WRAP) { - return State::STENCIL_OP_DECR_SAT; - } else if (stencilOp == GL_INVERT) { - return State::STENCIL_OP_INVERT; - } else if (stencilOp == GL_INCR) { - return State::STENCIL_OP_INCR; - } else if (stencilOp == GL_DECR) { - return State::STENCIL_OP_DECR; - } - - return State::STENCIL_OP_KEEP; -} - -State::BlendOp blendOpFromGL(GLenum blendOp) { - if (blendOp == GL_FUNC_ADD) { - return State::BLEND_OP_ADD; - } else if (blendOp == GL_FUNC_SUBTRACT) { - return State::BLEND_OP_SUBTRACT; - } else if (blendOp == GL_FUNC_REVERSE_SUBTRACT) { - return State::BLEND_OP_REV_SUBTRACT; - } else if (blendOp == GL_MIN) { - return State::BLEND_OP_MIN; - } else if (blendOp == GL_MAX) { - return State::BLEND_OP_MAX; - } - - return State::BLEND_OP_ADD; -} - -State::BlendArg blendArgFromGL(GLenum blendArg) { - if (blendArg == GL_ZERO) { - return State::ZERO; - } else if (blendArg == GL_ONE) { - return State::ONE; - } else if (blendArg == GL_SRC_COLOR) { - return State::SRC_COLOR; - } else if (blendArg == GL_ONE_MINUS_SRC_COLOR) { - return State::INV_SRC_COLOR; - } else if (blendArg == GL_DST_COLOR) { - return State::DEST_COLOR; - } else if (blendArg == GL_ONE_MINUS_DST_COLOR) { - return State::INV_DEST_COLOR; - } else if (blendArg == GL_SRC_ALPHA) { - return State::SRC_ALPHA; - } else if (blendArg == GL_ONE_MINUS_SRC_ALPHA) { - return State::INV_SRC_ALPHA; - } else if (blendArg == GL_DST_ALPHA) { - return State::DEST_ALPHA; - } else if (blendArg == GL_ONE_MINUS_DST_ALPHA) { - return State::INV_DEST_ALPHA; - } else if (blendArg == GL_CONSTANT_COLOR) { - return State::FACTOR_COLOR; - } else if (blendArg == GL_ONE_MINUS_CONSTANT_COLOR) { - return State::INV_FACTOR_COLOR; - } else if (blendArg == GL_CONSTANT_ALPHA) { - return State::FACTOR_ALPHA; - } else if (blendArg == GL_ONE_MINUS_CONSTANT_ALPHA) { - return State::INV_FACTOR_ALPHA; - } - - return State::ONE; -} - -void GLBackend::getCurrentGLState(State::Data& state) { - { - GLint modes[2]; - glGetIntegerv(GL_POLYGON_MODE, modes); - if (modes[0] == GL_FILL) { - state.fillMode = State::FILL_FACE; - } else { - if (modes[0] == GL_LINE) { - state.fillMode = State::FILL_LINE; - } else { - state.fillMode = State::FILL_POINT; - } - } - } - { - if (glIsEnabled(GL_CULL_FACE)) { - GLint mode; - glGetIntegerv(GL_CULL_FACE_MODE, &mode); - state.cullMode = (mode == GL_FRONT ? State::CULL_FRONT : State::CULL_BACK); - } else { - state.cullMode = State::CULL_NONE; - } - } - { - GLint winding; - glGetIntegerv(GL_FRONT_FACE, &winding); - state.frontFaceClockwise = (winding == GL_CW); - state.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP); - state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); - state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE); - state.antialisedLineEnable = glIsEnabled(GL_LINE_SMOOTH); - } - { - if (glIsEnabled(GL_POLYGON_OFFSET_FILL)) { - glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &state.depthBiasSlopeScale); - glGetFloatv(GL_POLYGON_OFFSET_UNITS, &state.depthBias); - } - } - { - GLboolean isEnabled = glIsEnabled(GL_DEPTH_TEST); - GLboolean writeMask; - glGetBooleanv(GL_DEPTH_WRITEMASK, &writeMask); - GLint func; - glGetIntegerv(GL_DEPTH_FUNC, &func); - - state.depthTest = State::DepthTest(isEnabled, writeMask, comparisonFuncFromGL(func)); - } - { - GLboolean isEnabled = glIsEnabled(GL_STENCIL_TEST); - - GLint frontWriteMask; - GLint frontReadMask; - GLint frontRef; - GLint frontFail; - GLint frontDepthFail; - GLint frontPass; - GLint frontFunc; - glGetIntegerv(GL_STENCIL_WRITEMASK, &frontWriteMask); - glGetIntegerv(GL_STENCIL_VALUE_MASK, &frontReadMask); - glGetIntegerv(GL_STENCIL_REF, &frontRef); - glGetIntegerv(GL_STENCIL_FAIL, &frontFail); - glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &frontDepthFail); - glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &frontPass); - glGetIntegerv(GL_STENCIL_FUNC, &frontFunc); - - GLint backWriteMask; - GLint backReadMask; - GLint backRef; - GLint backFail; - GLint backDepthFail; - GLint backPass; - GLint backFunc; - glGetIntegerv(GL_STENCIL_BACK_WRITEMASK, &backWriteMask); - glGetIntegerv(GL_STENCIL_BACK_VALUE_MASK, &backReadMask); - glGetIntegerv(GL_STENCIL_BACK_REF, &backRef); - glGetIntegerv(GL_STENCIL_BACK_FAIL, &backFail); - glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_FAIL, &backDepthFail); - glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_PASS, &backPass); - glGetIntegerv(GL_STENCIL_BACK_FUNC, &backFunc); - - state.stencilActivation = State::StencilActivation(isEnabled, frontWriteMask, backWriteMask); - state.stencilTestFront = State::StencilTest(frontRef, frontReadMask, comparisonFuncFromGL(frontFunc), stencilOpFromGL(frontFail), stencilOpFromGL(frontDepthFail), stencilOpFromGL(frontPass)); - state.stencilTestBack = State::StencilTest(backRef, backReadMask, comparisonFuncFromGL(backFunc), stencilOpFromGL(backFail), stencilOpFromGL(backDepthFail), stencilOpFromGL(backPass)); - } - { - GLint mask = 0xFFFFFFFF; - -#ifdef GPU_PROFILE_CORE - if (glIsEnabled(GL_SAMPLE_MASK)) { - glGetIntegerv(GL_SAMPLE_MASK, &mask); - state.sampleMask = mask; - } -#endif - state.sampleMask = mask; - } - { - state.alphaToCoverageEnable = glIsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE); - } - { - GLboolean isEnabled = glIsEnabled(GL_BLEND); - GLint srcRGB; - GLint srcA; - GLint dstRGB; - GLint dstA; - glGetIntegerv(GL_BLEND_SRC_RGB, &srcRGB); - glGetIntegerv(GL_BLEND_SRC_ALPHA, &srcA); - glGetIntegerv(GL_BLEND_DST_RGB, &dstRGB); - glGetIntegerv(GL_BLEND_DST_ALPHA, &dstA); - - GLint opRGB; - GLint opA; - glGetIntegerv(GL_BLEND_EQUATION_RGB, &opRGB); - glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &opA); - - state.blendFunction = State::BlendFunction(isEnabled, - blendArgFromGL(srcRGB), blendOpFromGL(opRGB), blendArgFromGL(dstRGB), - blendArgFromGL(srcA), blendOpFromGL(opA), blendArgFromGL(dstA)); - } - { - GLboolean mask[4]; - glGetBooleanv(GL_COLOR_WRITEMASK, mask); - state.colorWriteMask = (mask[0] ? State::WRITE_RED : 0) - | (mask[1] ? State::WRITE_GREEN : 0) - | (mask[2] ? State::WRITE_BLUE : 0) - | (mask[3] ? State::WRITE_ALPHA : 0); - } - - (void) CHECK_GL_ERROR(); -} - void GLBackend::syncPipelineStateCache() { State::Data state; @@ -500,21 +49,12 @@ void GLBackend::syncPipelineStateCache() { _pipeline._stateSignatureCache = signature; } -static GLenum GL_COMPARISON_FUNCTIONS[] = { - GL_NEVER, - GL_LESS, - GL_EQUAL, - GL_LEQUAL, - GL_GREATER, - GL_NOTEQUAL, - GL_GEQUAL, - GL_ALWAYS }; void GLBackend::do_setStateFillMode(int32 mode) { if (_pipeline._stateCache.fillMode != mode) { static GLenum GL_FILL_MODES[] = { GL_POINT, GL_LINE, GL_FILL }; glPolygonMode(GL_FRONT_AND_BACK, GL_FILL_MODES[mode]); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.fillMode = State::FillMode(mode); } @@ -530,7 +70,7 @@ void GLBackend::do_setStateCullMode(int32 mode) { glEnable(GL_CULL_FACE); glCullFace(GL_CULL_MODES[mode]); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.cullMode = State::CullMode(mode); } @@ -540,8 +80,8 @@ void GLBackend::do_setStateFrontFaceClockwise(bool isClockwise) { if (_pipeline._stateCache.frontFaceClockwise != isClockwise) { static GLenum GL_FRONT_FACES[] = { GL_CCW, GL_CW }; glFrontFace(GL_FRONT_FACES[isClockwise]); - (void) CHECK_GL_ERROR(); - + (void)CHECK_GL_ERROR(); + _pipeline._stateCache.frontFaceClockwise = isClockwise; } } @@ -553,7 +93,7 @@ void GLBackend::do_setStateDepthClampEnable(bool enable) { } else { glDisable(GL_DEPTH_CLAMP); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.depthClampEnable = enable; } @@ -566,7 +106,7 @@ void GLBackend::do_setStateScissorEnable(bool enable) { } else { glDisable(GL_SCISSOR_TEST); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.scissorEnable = enable; } @@ -579,7 +119,7 @@ void GLBackend::do_setStateMultisampleEnable(bool enable) { } else { glDisable(GL_MULTISAMPLE); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.multisampleEnable = enable; } @@ -592,14 +132,14 @@ void GLBackend::do_setStateAntialiasedLineEnable(bool enable) { } else { glDisable(GL_LINE_SMOOTH); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.antialisedLineEnable = enable; } } void GLBackend::do_setStateDepthBias(Vec2 bias) { - if ( (bias.x != _pipeline._stateCache.depthBias) || (bias.y != _pipeline._stateCache.depthBiasSlopeScale)) { + if ((bias.x != _pipeline._stateCache.depthBias) || (bias.y != _pipeline._stateCache.depthBiasSlopeScale)) { if ((bias.x != 0.0f) || (bias.y != 0.0f)) { glEnable(GL_POLYGON_OFFSET_FILL); glEnable(GL_POLYGON_OFFSET_LINE); @@ -610,10 +150,10 @@ void GLBackend::do_setStateDepthBias(Vec2 bias) { glDisable(GL_POLYGON_OFFSET_LINE); glDisable(GL_POLYGON_OFFSET_POINT); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); - _pipeline._stateCache.depthBias = bias.x; - _pipeline._stateCache.depthBiasSlopeScale = bias.y; + _pipeline._stateCache.depthBias = bias.x; + _pipeline._stateCache.depthBiasSlopeScale = bias.y; } } @@ -622,15 +162,15 @@ void GLBackend::do_setStateDepthTest(State::DepthTest test) { if (test.isEnabled()) { glEnable(GL_DEPTH_TEST); glDepthMask(test.getWriteMask()); - glDepthFunc(GL_COMPARISON_FUNCTIONS[test.getFunction()]); + glDepthFunc(COMPARISON_TO_GL[test.getFunction()]); } else { glDisable(GL_DEPTH_TEST); } if (CHECK_GL_ERROR()) { qDebug() << "DepthTest" << (test.isEnabled() ? "Enabled" : "Disabled") - << "Mask=" << (test.getWriteMask() ? "Write" : "no Write") - << "Func=" << test.getFunction() - << "Raw=" << test.getRaw(); + << "Mask=" << (test.getWriteMask() ? "Write" : "no Write") + << "Func=" << test.getFunction() + << "Raw=" << test.getRaw(); } _pipeline._stateCache.depthTest = test; @@ -638,7 +178,7 @@ void GLBackend::do_setStateDepthTest(State::DepthTest test) { } void GLBackend::do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest) { - + if ((_pipeline._stateCache.stencilActivation != activation) || (_pipeline._stateCache.stencilTestFront != frontTest) || (_pipeline._stateCache.stencilTestBack != backTest)) { @@ -665,18 +205,18 @@ void GLBackend::do_setStateStencil(State::StencilActivation activation, State::S if (frontTest != backTest) { glStencilOpSeparate(GL_FRONT, STENCIL_OPS[frontTest.getFailOp()], STENCIL_OPS[frontTest.getPassOp()], STENCIL_OPS[frontTest.getDepthFailOp()]); - glStencilFuncSeparate(GL_FRONT, GL_COMPARISON_FUNCTIONS[frontTest.getFunction()], frontTest.getReference(), frontTest.getReadMask()); + glStencilFuncSeparate(GL_FRONT, COMPARISON_TO_GL[frontTest.getFunction()], frontTest.getReference(), frontTest.getReadMask()); glStencilOpSeparate(GL_BACK, STENCIL_OPS[backTest.getFailOp()], STENCIL_OPS[backTest.getPassOp()], STENCIL_OPS[backTest.getDepthFailOp()]); - glStencilFuncSeparate(GL_BACK, GL_COMPARISON_FUNCTIONS[backTest.getFunction()], backTest.getReference(), backTest.getReadMask()); + glStencilFuncSeparate(GL_BACK, COMPARISON_TO_GL[backTest.getFunction()], backTest.getReference(), backTest.getReadMask()); } else { glStencilOp(STENCIL_OPS[frontTest.getFailOp()], STENCIL_OPS[frontTest.getPassOp()], STENCIL_OPS[frontTest.getDepthFailOp()]); - glStencilFunc(GL_COMPARISON_FUNCTIONS[frontTest.getFunction()], frontTest.getReference(), frontTest.getReadMask()); + glStencilFunc(COMPARISON_TO_GL[frontTest.getFunction()], frontTest.getReference(), frontTest.getReadMask()); } } else { glDisable(GL_STENCIL_TEST); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.stencilActivation = activation; _pipeline._stateCache.stencilTestFront = frontTest; @@ -691,7 +231,7 @@ void GLBackend::do_setStateAlphaToCoverageEnable(bool enable) { } else { glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.alphaToCoverageEnable = enable; } @@ -699,15 +239,13 @@ void GLBackend::do_setStateAlphaToCoverageEnable(bool enable) { void GLBackend::do_setStateSampleMask(uint32 mask) { if (_pipeline._stateCache.sampleMask != mask) { -#ifdef GPU_CORE_PROFILE if (mask == 0xFFFFFFFF) { glDisable(GL_SAMPLE_MASK); } else { glEnable(GL_SAMPLE_MASK); glSampleMaski(0, mask); } - (void) CHECK_GL_ERROR(); -#endif + (void)CHECK_GL_ERROR(); _pipeline._stateCache.sampleMask = mask; } } @@ -717,40 +255,16 @@ void GLBackend::do_setStateBlend(State::BlendFunction function) { if (function.isEnabled()) { glEnable(GL_BLEND); - static GLenum GL_BLEND_OPS[] = { - GL_FUNC_ADD, - GL_FUNC_SUBTRACT, - GL_FUNC_REVERSE_SUBTRACT, - GL_MIN, - GL_MAX }; + glBlendEquationSeparate(BLEND_OPS_TO_GL[function.getOperationColor()], BLEND_OPS_TO_GL[function.getOperationAlpha()]); + (void)CHECK_GL_ERROR(); - glBlendEquationSeparate(GL_BLEND_OPS[function.getOperationColor()], GL_BLEND_OPS[function.getOperationAlpha()]); - (void) CHECK_GL_ERROR(); - static GLenum BLEND_ARGS[] = { - GL_ZERO, - GL_ONE, - GL_SRC_COLOR, - GL_ONE_MINUS_SRC_COLOR, - GL_SRC_ALPHA, - GL_ONE_MINUS_SRC_ALPHA, - GL_DST_ALPHA, - GL_ONE_MINUS_DST_ALPHA, - GL_DST_COLOR, - GL_ONE_MINUS_DST_COLOR, - GL_SRC_ALPHA_SATURATE, - GL_CONSTANT_COLOR, - GL_ONE_MINUS_CONSTANT_COLOR, - GL_CONSTANT_ALPHA, - GL_ONE_MINUS_CONSTANT_ALPHA, - }; - - glBlendFuncSeparate(BLEND_ARGS[function.getSourceColor()], BLEND_ARGS[function.getDestinationColor()], - BLEND_ARGS[function.getSourceAlpha()], BLEND_ARGS[function.getDestinationAlpha()]); + glBlendFuncSeparate(BLEND_ARGS_TO_GL[function.getSourceColor()], BLEND_ARGS_TO_GL[function.getDestinationColor()], + BLEND_ARGS_TO_GL[function.getSourceAlpha()], BLEND_ARGS_TO_GL[function.getDestinationAlpha()]); } else { glDisable(GL_BLEND); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.blendFunction = function; } @@ -759,10 +273,10 @@ void GLBackend::do_setStateBlend(State::BlendFunction function) { void GLBackend::do_setStateColorWriteMask(uint32 mask) { if (_pipeline._stateCache.colorWriteMask != mask) { glColorMask(mask & State::ColorMask::WRITE_RED, - mask & State::ColorMask::WRITE_GREEN, - mask & State::ColorMask::WRITE_BLUE, - mask & State::ColorMask::WRITE_ALPHA ); - (void) CHECK_GL_ERROR(); + mask & State::ColorMask::WRITE_GREEN, + mask & State::ColorMask::WRITE_BLUE, + mask & State::ColorMask::WRITE_ALPHA); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.colorWriteMask = mask; } @@ -771,12 +285,12 @@ void GLBackend::do_setStateColorWriteMask(uint32 mask) { void GLBackend::do_setStateBlendFactor(Batch& batch, size_t paramOffset) { Vec4 factor(batch._params[paramOffset + 0]._float, - batch._params[paramOffset + 1]._float, - batch._params[paramOffset + 2]._float, - batch._params[paramOffset + 3]._float); + batch._params[paramOffset + 1]._float, + batch._params[paramOffset + 2]._float, + batch._params[paramOffset + 3]._float); glBlendColor(factor.x, factor.y, factor.z, factor.w); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } void GLBackend::do_setStateScissorRect(Batch& batch, size_t paramOffset) { @@ -790,5 +304,6 @@ void GLBackend::do_setStateScissorRect(Batch& batch, size_t paramOffset) { } } glScissor(rect.x, rect.y, rect.z, rect.w); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } + diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp index 952e5bad8f..6d9b5fd2c7 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp @@ -9,591 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" - -#include -#include -#include - -#include "GLBackendShared.h" - -#include "GLBackendTextureTransfer.h" +#include "GLTexture.h" using namespace gpu; using namespace gpu::gl; - -GLenum gpuToGLTextureType(const Texture& texture) { - switch (texture.getType()) { - case Texture::TEX_2D: - return GL_TEXTURE_2D; - break; - - case Texture::TEX_CUBE: - return GL_TEXTURE_CUBE_MAP; - break; - - default: - qFatal("Unsupported texture type"); - } - Q_UNREACHABLE(); - return GL_TEXTURE_2D; -} - -GLuint allocateSingleTexture() { - Backend::incrementTextureGPUCount(); - GLuint result; - glGenTextures(1, &result); - return result; -} - - -// FIXME placeholder for texture memory over-use -#define DEFAULT_MAX_MEMORY_MB 256 - -float GLBackend::GLTexture::getMemoryPressure() { - // Check for an explicit memory limit - auto availableTextureMemory = Texture::getAllowedGPUMemoryUsage(); - - // If no memory limit has been set, use a percentage of the total dedicated memory - if (!availableTextureMemory) { - auto totalGpuMemory = GLBackend::getDedicatedMemory(); - - // If no limit has been explicitly set, and the dedicated memory can't be determined, - // just use a fallback fixed value of 256 MB - if (!totalGpuMemory) { - totalGpuMemory = MB_TO_BYTES(DEFAULT_MAX_MEMORY_MB); - } - - // Allow 75% of all available GPU memory to be consumed by textures - // FIXME overly conservative? - availableTextureMemory = (totalGpuMemory >> 2) * 3; - } - - // Return the consumed texture memory divided by the available texture memory. - auto consumedGpuMemory = Context::getTextureGPUMemoryUsage(); - return (float)consumedGpuMemory / (float)availableTextureMemory; -} - -GLBackend::GLTexture::DownsampleSource::DownsampleSource(GLTexture& oldTexture) : - _texture(oldTexture._privateTexture), - _minMip(oldTexture._minMip), - _maxMip(oldTexture._maxMip) -{ - // Take ownership of the GL texture - oldTexture._texture = oldTexture._privateTexture = 0; -} - -GLBackend::GLTexture::DownsampleSource::~DownsampleSource() { - if (_texture) { - Backend::decrementTextureGPUCount(); - glDeleteTextures(1, &_texture); - } -} - -const GLenum GLBackend::GLTexture::CUBE_FACE_LAYOUT[6] = { - GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, - GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, - GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z -}; - -static std::map _textureCountByMips; -static uint16 _currentMaxMipCount { 0 }; - -GLBackend::GLTexture::GLTexture(bool transferrable, const Texture& texture, bool init) : - _storageStamp(texture.getStamp()), - _target(gpuToGLTextureType(texture)), - _maxMip(texture.maxMip()), - _minMip(texture.minMip()), - _transferrable(transferrable), - _virtualSize(texture.evalTotalSize()), - _size(_virtualSize), - _gpuTexture(texture) -{ - Q_UNUSED(init); - - if (_transferrable) { - uint16 mipCount = usedMipLevels(); - _currentMaxMipCount = std::max(_currentMaxMipCount, mipCount); - if (!_textureCountByMips.count(mipCount)) { - _textureCountByMips[mipCount] = 1; - } else { - ++_textureCountByMips[mipCount]; - } - } else { - withPreservedTexture([&] { - createTexture(); - }); - _contentStamp = _gpuTexture.getDataStamp(); - postTransfer(); - } - - Backend::updateTextureGPUMemoryUsage(0, _size); - Backend::updateTextureGPUVirtualMemoryUsage(0, _virtualSize); -} - -// Create the texture and allocate storage -GLBackend::GLTexture::GLTexture(bool transferrable, const Texture& texture) : - GLTexture(transferrable, texture, true) -{ - Backend::setGPUObject(texture, this); -} - -// Create the texture and copy from the original higher resolution version -GLBackend::GLTexture::GLTexture(GLTexture& originalTexture, const gpu::Texture& texture) : - GLTexture(originalTexture._transferrable, texture, true) -{ - if (!originalTexture._texture) { - qFatal("Invalid original texture"); - } - Q_ASSERT(_minMip >= originalTexture._minMip); - // Our downsampler takes ownership of the texture - _downsampleSource = std::make_shared(originalTexture); - _texture = _downsampleSource->_texture; - - // Set the GPU object last because that implicitly destroys the originalTexture object - Backend::setGPUObject(texture, this); -} - -GLBackend::GLTexture::~GLTexture() { - if (_privateTexture != 0) { - Backend::decrementTextureGPUCount(); - glDeleteTextures(1, &_privateTexture); - } - - if (_transferrable) { - uint16 mipCount = usedMipLevels(); - Q_ASSERT(_textureCountByMips.count(mipCount)); - auto& numTexturesForMipCount = _textureCountByMips[mipCount]; - --numTexturesForMipCount; - if (0 == numTexturesForMipCount) { - _textureCountByMips.erase(mipCount); - if (mipCount == _currentMaxMipCount) { - _currentMaxMipCount = _textureCountByMips.rbegin()->first; - } - } - } - - Backend::updateTextureGPUMemoryUsage(_size, 0); - Backend::updateTextureGPUVirtualMemoryUsage(_virtualSize, 0); -} - -const std::vector& GLBackend::GLTexture::getFaceTargets() const { - static std::vector cubeFaceTargets { - GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, - GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, - GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z - }; - static std::vector faceTargets { - GL_TEXTURE_2D - }; - switch (_target) { - case GL_TEXTURE_2D: - return faceTargets; - case GL_TEXTURE_CUBE_MAP: - return cubeFaceTargets; - default: - Q_UNREACHABLE(); - break; - } - Q_UNREACHABLE(); - return faceTargets; -} - -void GLBackend::GLTexture::withPreservedTexture(std::function f) { - GLint boundTex = -1; - switch (_target) { - case GL_TEXTURE_2D: - glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); - break; - - case GL_TEXTURE_CUBE_MAP: - glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex); - break; - - default: - qFatal("Unsupported texture type"); - } - (void)CHECK_GL_ERROR(); - - f(); - - glBindTexture(_target, boundTex); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::GLTexture::createTexture() { - _privateTexture = allocateSingleTexture(); - - glBindTexture(_target, _privateTexture); - (void)CHECK_GL_ERROR(); - - allocateStorage(); - (void)CHECK_GL_ERROR(); - - syncSampler(_gpuTexture.getSampler(), _gpuTexture.getType(), this); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::GLTexture::allocateStorage() { - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuTexture.getTexelFormat()); - glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); - (void)CHECK_GL_ERROR(); - glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); - (void)CHECK_GL_ERROR(); - if (GLEW_VERSION_4_2 && !_gpuTexture.getTexelFormat().isCompressed()) { - // Get the dimensions, accounting for the downgrade level - Vec3u dimensions = _gpuTexture.evalMipDimensions(_minMip); - glTexStorage2D(_target, usedMipLevels(), texelFormat.internalFormat, dimensions.x, dimensions.y); - (void)CHECK_GL_ERROR(); - } else { - for (uint16_t l = _minMip; l <= _maxMip; l++) { - // Get the mip level dimensions, accounting for the downgrade level - Vec3u dimensions = _gpuTexture.evalMipDimensions(l); - for (GLenum target : getFaceTargets()) { - glTexImage2D(target, l - _minMip, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, NULL); - (void)CHECK_GL_ERROR(); - } - } - } -} - - -void GLBackend::GLTexture::setSize(GLuint size) { - Backend::updateTextureGPUMemoryUsage(_size, size); - _size = size; -} - -void GLBackend::GLTexture::updateSize() { - setSize(_virtualSize); - if (!_texture) { - return; - } - - if (_gpuTexture.getTexelFormat().isCompressed()) { - GLenum proxyType = GL_TEXTURE_2D; - GLuint numFaces = 1; - if (_gpuTexture.getType() == gpu::Texture::TEX_CUBE) { - proxyType = CUBE_FACE_LAYOUT[0]; - numFaces = CUBE_NUM_FACES; - } - GLint gpuSize{ 0 }; - glGetTexLevelParameteriv(proxyType, 0, GL_TEXTURE_COMPRESSED, &gpuSize); - (void)CHECK_GL_ERROR(); - - if (gpuSize) { - for (GLuint level = _minMip; level < _maxMip; level++) { - GLint levelSize{ 0 }; - glGetTexLevelParameteriv(proxyType, level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &levelSize); - levelSize *= numFaces; - - if (levelSize <= 0) { - break; - } - gpuSize += levelSize; - } - (void)CHECK_GL_ERROR(); - setSize(gpuSize); - return; - } - } -} - -bool GLBackend::GLTexture::isInvalid() const { - return _storageStamp < _gpuTexture.getStamp(); -} - -bool GLBackend::GLTexture::isOutdated() const { - return GLTexture::Idle == _syncState && _contentStamp < _gpuTexture.getDataStamp(); -} - -bool GLBackend::GLTexture::isOverMaxMemory() const { - // FIXME switch to using the max mip count used from the previous frame - if (usedMipLevels() < _currentMaxMipCount) { - return false; - } - Q_ASSERT(usedMipLevels() == _currentMaxMipCount); - - if (getMemoryPressure() < 1.0f) { - return false; - } - - return true; -} - -bool GLBackend::GLTexture::isReady() const { - // If we have an invalid texture, we're never ready - if (isInvalid()) { - return false; - } - - // If we're out of date, but the transfer is in progress, report ready - // as a special case - auto syncState = _syncState.load(); - - if (isOutdated()) { - return Idle != syncState; - } - - if (Idle != syncState) { - return false; - } - - return true; -} - -// Move content bits from the CPU to the GPU for a given mip / face -void GLBackend::GLTexture::transferMip(uint16_t mipLevel, uint8_t face) const { - auto mip = _gpuTexture.accessStoredMipFace(mipLevel, face); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuTexture.getTexelFormat(), mip->getFormat()); - //GLenum target = getFaceTargets()[face]; - GLenum target = _target == GL_TEXTURE_2D ? GL_TEXTURE_2D : CUBE_FACE_LAYOUT[face]; - auto size = _gpuTexture.evalMipDimensions(mipLevel); - glTexSubImage2D(target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); - (void)CHECK_GL_ERROR(); -} - -// This should never happen on the main thread -// Move content bits from the CPU to the GPU -void GLBackend::GLTexture::transfer() const { - PROFILE_RANGE(__FUNCTION__); - //qDebug() << "Transferring texture: " << _privateTexture; - // Need to update the content of the GPU object from the source sysmem of the texture - if (_contentStamp >= _gpuTexture.getDataStamp()) { - return; - } - - glBindTexture(_target, _privateTexture); - (void)CHECK_GL_ERROR(); - - if (_downsampleSource) { - GLuint fbo { 0 }; - glGenFramebuffers(1, &fbo); - (void)CHECK_GL_ERROR(); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - (void)CHECK_GL_ERROR(); - // Find the distance between the old min mip and the new one - uint16 mipOffset = _minMip - _downsampleSource->_minMip; - for (uint16 i = _minMip; i <= _maxMip; ++i) { - uint16 targetMip = i - _minMip; - uint16 sourceMip = targetMip + mipOffset; - Vec3u dimensions = _gpuTexture.evalMipDimensions(i); - for (GLenum target : getFaceTargets()) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, _downsampleSource->_texture, sourceMip); - (void)CHECK_GL_ERROR(); - glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, dimensions.x, dimensions.y); - (void)CHECK_GL_ERROR(); - } - } - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); - } else { - // GO through the process of allocating the correct storage and/or update the content - switch (_gpuTexture.getType()) { - case Texture::TEX_2D: - { - for (uint16_t i = _minMip; i <= _maxMip; ++i) { - if (_gpuTexture.isStoredMipFaceAvailable(i)) { - transferMip(i); - } - } - } - break; - - case Texture::TEX_CUBE: - // transfer pixels from each faces - for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuTexture.isStoredMipFaceAvailable(i, f)) { - transferMip(i, f); - } - } - } - break; - - default: - qCWarning(gpugllogging) << __FUNCTION__ << " case for Texture Type " << _gpuTexture.getType() << " not supported"; - break; - } - } - if (_gpuTexture.isAutogenerateMips()) { - glGenerateMipmap(_target); - (void)CHECK_GL_ERROR(); - } -} - -// Do any post-transfer operations that might be required on the main context / rendering thread -void GLBackend::GLTexture::postTransfer() { - setSyncState(GLTexture::Idle); - - // The public gltexture becaomes available - _texture = _privateTexture; - - _downsampleSource.reset(); - - // At this point the mip pixels have been loaded, we can notify the gpu texture to abandon it's memory - switch (_gpuTexture.getType()) { - case Texture::TEX_2D: - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuTexture.isStoredMipFaceAvailable(i)) { - _gpuTexture.notifyMipFaceGPULoaded(i); - } - } - break; - - case Texture::TEX_CUBE: - // transfer pixels from each faces - for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuTexture.isStoredMipFaceAvailable(i, f)) { - _gpuTexture.notifyMipFaceGPULoaded(i, f); - } - } - } - break; - - default: - qCWarning(gpugllogging) << __FUNCTION__ << " case for Texture Type " << _gpuTexture.getType() << " not supported"; - break; - } -} - -GLBackend::GLTexture* GLBackend::syncGPUObject(const TexturePointer& texturePointer, bool needTransfer) { - const Texture& texture = *texturePointer; - if (!texture.isDefined()) { - // NO texture definition yet so let's avoid thinking - return nullptr; - } - - // If the object hasn't been created, or the object definition is out of date, drop and re-create - GLTexture* object = Backend::getGPUObject(texture); - - // Create the texture if need be (force re-creation if the storage stamp changes - // for easier use of immutable storage) - if (!object || object->isInvalid()) { - // This automatically any previous texture - object = new GLTexture(needTransfer, texture); - } - - // Object maybe doens't neet to be tranasferred after creation - if (!object->_transferrable) { - return object; - } - - // If we just did a transfer, return the object after doing post-transfer work - if (GLTexture::Transferred == object->getSyncState()) { - object->postTransfer(); - return object; - } - - if (object->isReady()) { - // Do we need to reduce texture memory usage? - if (object->isOverMaxMemory() && texturePointer->incremementMinMip()) { - // This automatically destroys the old texture - object = new GLTexture(*object, texture); - _textureTransferHelper->transferTexture(texturePointer); - } - } else if (object->isOutdated()) { - // Object might be outdated, if so, start the transfer - // (outdated objects that are already in transfer will have reported 'true' for ready() - _textureTransferHelper->transferTexture(texturePointer); - } - - return object; -} - -std::shared_ptr GLBackend::_textureTransferHelper; - -void GLBackend::initTextureTransferHelper() { - _textureTransferHelper = std::make_shared(); -} - -GLuint GLBackend::getTextureID(const TexturePointer& texture, bool sync) { - if (!texture) { - return 0; - } - GLTexture* object { nullptr }; - if (sync) { - object = GLBackend::syncGPUObject(texture); - } else { - object = Backend::getGPUObject(*texture); - } - if (object) { - if (object->getSyncState() == GLTexture::Idle) { - return object->_texture; - } else if (object->_downsampleSource) { - return object->_downsampleSource->_texture; - } else { - return 0; - } - } else { - return 0; - } -} - -void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, const GLTexture* object) { - if (!object) return; - - class GLFilterMode { - public: - GLint minFilter; - GLint magFilter; - }; - static const GLFilterMode filterModes[Sampler::NUM_FILTERS] = { - { GL_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_POINT, - { GL_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR, - { GL_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT, - { GL_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR, - - { GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_MIP_POINT, - { GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_MAG_POINT_MIP_LINEAR, - { GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, - { GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_POINT_MAG_MIP_LINEAR, - { GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_MIP_POINT, - { GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, - { GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR_MIP_POINT, - { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_MIP_LINEAR, - { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR } //FILTER_ANISOTROPIC, - }; - - auto fm = filterModes[sampler.getFilter()]; - glTexParameteri(object->_target, GL_TEXTURE_MIN_FILTER, fm.minFilter); - glTexParameteri(object->_target, GL_TEXTURE_MAG_FILTER, fm.magFilter); - - static const GLenum comparisonFuncs[NUM_COMPARISON_FUNCS] = { - GL_NEVER, - GL_LESS, - GL_EQUAL, - GL_LEQUAL, - GL_GREATER, - GL_NOTEQUAL, - GL_GEQUAL, - GL_ALWAYS }; - - if (sampler.doComparison()) { - glTexParameteri(object->_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - glTexParameteri(object->_target, GL_TEXTURE_COMPARE_FUNC, comparisonFuncs[sampler.getComparisonFunction()]); - } else { - glTexParameteri(object->_target, GL_TEXTURE_COMPARE_MODE, GL_NONE); - } - - static const GLenum wrapModes[Sampler::NUM_WRAP_MODES] = { - GL_REPEAT, // WRAP_REPEAT, - GL_MIRRORED_REPEAT, // WRAP_MIRROR, - GL_CLAMP_TO_EDGE, // WRAP_CLAMP, - GL_CLAMP_TO_BORDER, // WRAP_BORDER, - GL_MIRROR_CLAMP_TO_EDGE_EXT }; // WRAP_MIRROR_ONCE, - - glTexParameteri(object->_target, GL_TEXTURE_WRAP_S, wrapModes[sampler.getWrapModeU()]); - glTexParameteri(object->_target, GL_TEXTURE_WRAP_T, wrapModes[sampler.getWrapModeV()]); - glTexParameteri(object->_target, GL_TEXTURE_WRAP_R, wrapModes[sampler.getWrapModeW()]); - - glTexParameterfv(object->_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); - glTexParameteri(object->_target, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset()); - glTexParameterf(object->_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); - glTexParameterf(object->_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); - glTexParameterf(object->_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); -} - void GLBackend::do_generateTextureMips(Batch& batch, size_t paramOffset) { TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); if (!resourceTexture) { @@ -601,28 +21,10 @@ void GLBackend::do_generateTextureMips(Batch& batch, size_t paramOffset) { } // DO not transfer the texture, this call is expected for rendering texture - GLTexture* object = GLBackend::syncGPUObject(resourceTexture, false); + GLTexture* object = syncGPUObject(resourceTexture, false); if (!object) { return; } - // IN 4.1 we still need to find an available slot - auto freeSlot = _resource.findEmptyTextureSlot(); - auto bindingSlot = (freeSlot < 0 ? 0 : freeSlot); - glActiveTexture(GL_TEXTURE0 + bindingSlot); - glBindTexture(object->_target, object->_texture); - - glGenerateMipmap(object->_target); - - if (freeSlot < 0) { - // If had to use slot 0 then restore state - GLTexture* boundObject = GLBackend::syncGPUObject(_resource._textures[0]); - if (boundObject) { - glBindTexture(boundObject->_target, boundObject->_texture); - } - } else { - // clean up - glBindTexture(object->_target, 0); - } - (void)CHECK_GL_ERROR(); + object->generateMips(); } diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp index e5f5672ad7..9487dc3289 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp @@ -9,7 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" -#include "GLBackendShared.h" using namespace gpu; using namespace gpu::gl; @@ -51,26 +50,11 @@ void GLBackend::do_setDepthRangeTransform(Batch& batch, size_t paramOffset) { } } -void GLBackend::initTransform() { - glGenBuffers(1, &_transform._objectBuffer); - glGenBuffers(1, &_transform._cameraBuffer); - glGenBuffers(1, &_transform._drawCallInfoBuffer); -#ifndef GPU_SSBO_DRAW_CALL_INFO - glGenTextures(1, &_transform._objectBufferTexture); -#endif - size_t cameraSize = sizeof(TransformStageState::CameraBufferElement); - while (_transform._cameraUboSize < cameraSize) { - _transform._cameraUboSize += _uboAlignment; - } -} - void GLBackend::killTransform() { glDeleteBuffers(1, &_transform._objectBuffer); glDeleteBuffers(1, &_transform._cameraBuffer); glDeleteBuffers(1, &_transform._drawCallInfoBuffer); -#ifndef GPU_SSBO_DRAW_CALL_INFO glDeleteTextures(1, &_transform._objectBufferTexture); -#endif } void GLBackend::syncTransformStateCache() { @@ -121,64 +105,6 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo _invalidView = _invalidProj = _invalidViewport = false; } -void GLBackend::TransformStageState::transfer(const Batch& batch) const { - // FIXME not thread safe - static std::vector bufferData; - if (!_cameras.empty()) { - bufferData.resize(_cameraUboSize * _cameras.size()); - for (size_t i = 0; i < _cameras.size(); ++i) { - memcpy(bufferData.data() + (_cameraUboSize * i), &_cameras[i], sizeof(CameraBufferElement)); - } - glBindBuffer(GL_UNIFORM_BUFFER, _cameraBuffer); - glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - } - - if (!batch._objects.empty()) { - auto byteSize = batch._objects.size() * sizeof(Batch::TransformObject); - bufferData.resize(byteSize); - memcpy(bufferData.data(), batch._objects.data(), byteSize); - -#ifdef GPU_SSBO_DRAW_CALL_INFO - glBindBuffer(GL_SHADER_STORAGE_BUFFER, _objectBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); -#else - glBindBuffer(GL_TEXTURE_BUFFER, _objectBuffer); - glBufferData(GL_TEXTURE_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_TEXTURE_BUFFER, 0); -#endif - } - - if (!batch._namedData.empty()) { - bufferData.clear(); - for (auto& data : batch._namedData) { - auto currentSize = bufferData.size(); - auto bytesToCopy = data.second.drawCallInfos.size() * sizeof(Batch::DrawCallInfo); - bufferData.resize(currentSize + bytesToCopy); - memcpy(bufferData.data() + currentSize, data.second.drawCallInfos.data(), bytesToCopy); - _drawCallInfoOffsets[data.first] = (GLvoid*)currentSize; - } - - glBindBuffer(GL_ARRAY_BUFFER, _drawCallInfoBuffer); - glBufferData(GL_ARRAY_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); - } - -#ifdef GPU_SSBO_DRAW_CALL_INFO - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, TRANSFORM_OBJECT_SLOT, _objectBuffer); -#else - glActiveTexture(GL_TEXTURE0 + TRANSFORM_OBJECT_SLOT); - glBindTexture(GL_TEXTURE_BUFFER, _objectBufferTexture); - glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _objectBuffer); -#endif - - CHECK_GL_ERROR(); - - // Make sure the current Camera offset is unknown before render Draw - _currentCameraOffset = INVALID_OFFSET; -} - void GLBackend::TransformStageState::update(size_t commandIndex, const StereoState& stereo) const { size_t offset = INVALID_OFFSET; while ((_camerasItr != _cameraOffsets.end()) && (commandIndex >= (*_camerasItr).first)) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBuffer.cpp b/libraries/gpu-gl/src/gpu/gl/GLBuffer.cpp new file mode 100644 index 0000000000..cd0f86a410 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLBuffer.cpp @@ -0,0 +1,28 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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 "GLBuffer.h" + +using namespace gpu; +using namespace gpu::gl; + +GLBuffer::~GLBuffer() { + glDeleteBuffers(1, &_id); + Backend::decrementBufferGPUCount(); + Backend::updateBufferGPUMemoryUsage(_size, 0); +} + +GLBuffer::GLBuffer(const Buffer& buffer, GLuint id) : + GLObject(buffer, id), + _size((GLuint)buffer._sysmem.getSize()), + _stamp(buffer._sysmem.getStamp()) +{ + Backend::incrementBufferGPUCount(); + Backend::updateBufferGPUMemoryUsage(0, _size); +} + diff --git a/libraries/gpu-gl/src/gpu/gl/GLBuffer.h b/libraries/gpu-gl/src/gpu/gl/GLBuffer.h new file mode 100644 index 0000000000..4783541b11 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLBuffer.h @@ -0,0 +1,57 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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_gpu_gl_GLBuffer_h +#define hifi_gpu_gl_GLBuffer_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLBuffer : public GLObject { +public: + template + static GLBufferType* sync(const Buffer& buffer) { + GLBufferType* object = Backend::getGPUObject(buffer); + + // Has the storage size changed? + if (!object || object->_stamp != buffer.getSysmem().getStamp()) { + object = new GLBufferType(buffer, object); + } + + if (0 != (buffer._flags & Buffer::DIRTY)) { + object->transfer(); + } + + return object; + } + + template + static GLuint getId(const Buffer& buffer) { + GLBuffer* bo = sync(buffer); + if (bo) { + return bo->_buffer; + } else { + return 0; + } + } + + const GLuint& _buffer { _id }; + const GLuint _size; + const Stamp _stamp; + + ~GLBuffer(); + + virtual void transfer() = 0; + +protected: + GLBuffer(const Buffer& buffer, GLuint id); +}; + +} } + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp new file mode 100644 index 0000000000..91f7fbd494 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp @@ -0,0 +1,39 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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 "GLFramebuffer.h" + +using namespace gpu; +using namespace gpu::gl; + + +bool GLFramebuffer::checkStatus(GLenum target) const { + bool result = false; + switch (_status) { + case GL_FRAMEBUFFER_COMPLETE: + // Success ! + result = true; + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; + break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; + break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; + break; + case GL_FRAMEBUFFER_UNSUPPORTED: + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; + break; + } + return result; +} diff --git a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h new file mode 100644 index 0000000000..d54c181c20 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h @@ -0,0 +1,76 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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_gpu_gl_GLFramebuffer_h +#define hifi_gpu_gl_GLFramebuffer_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLFramebuffer : public GLObject { +public: + template + static GLFramebufferType* sync(const Framebuffer& framebuffer) { + GLFramebufferType* object = Backend::getGPUObject(framebuffer); + + bool needsUpate { false }; + if (!object || + framebuffer.getDepthStamp() != object->_depthStamp || + framebuffer.getColorStamps() != object->_colorStamps) { + needsUpate = true; + } + + // If GPU object already created and in sync + if (!needsUpate) { + return object; + } else if (framebuffer.isEmpty()) { + // NO framebuffer definition yet so let's avoid thinking + return nullptr; + } + + // need to have a gpu object? + if (!object) { + // All is green, assign the gpuobject to the Framebuffer + object = new GLFramebufferType(framebuffer); + Backend::setGPUObject(framebuffer, object); + (void)CHECK_GL_ERROR(); + } + + object->update(); + return object; + } + + template + static GLuint getId(const Framebuffer& framebuffer) { + GLFramebufferType* fbo = sync(framebuffer); + if (fbo) { + return fbo->_id; + } else { + return 0; + } + } + + const GLuint& _fbo { _id }; + std::vector _colorBuffers; + Stamp _depthStamp { 0 }; + std::vector _colorStamps; + +protected: + GLenum _status { GL_FRAMEBUFFER_COMPLETE }; + virtual void update() = 0; + bool checkStatus(GLenum target) const; + + GLFramebuffer(const Framebuffer& framebuffer, GLuint id) : GLObject(framebuffer, id) {} + ~GLFramebuffer() { if (_id) { glDeleteFramebuffers(1, &_id); } }; + +}; + +} } + + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp new file mode 100644 index 0000000000..fa54e7c8fe --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp @@ -0,0 +1,55 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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 "GLPipeline.h" + +#include "GLShader.h" +#include "GLState.h" + +using namespace gpu; +using namespace gpu::gl; + +GLPipeline* GLPipeline::sync(const Pipeline& pipeline) { + GLPipeline* object = Backend::getGPUObject(pipeline); + + // If GPU object already created then good + if (object) { + return object; + } + + // No object allocated yet, let's see if it's worth it... + ShaderPointer shader = pipeline.getProgram(); + + // If this pipeline's shader has already failed to compile, don't try again + if (shader->compilationHasFailed()) { + return nullptr; + } + + GLShader* programObject = GLShader::sync(*shader); + if (programObject == nullptr) { + shader->setCompilationHasFailed(true); + return nullptr; + } + + StatePointer state = pipeline.getState(); + GLState* stateObject = GLState::sync(*state); + if (stateObject == nullptr) { + return nullptr; + } + + // Program and state are valid, we can create the pipeline object + if (!object) { + object = new GLPipeline(); + Backend::setGPUObject(pipeline, object); + } + + object->_program = programObject; + object->_state = stateObject; + + return object; +} diff --git a/libraries/gpu-gl/src/gpu/gl/GLPipeline.h b/libraries/gpu-gl/src/gpu/gl/GLPipeline.h new file mode 100644 index 0000000000..9ade2bb830 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLPipeline.h @@ -0,0 +1,26 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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_gpu_gl_GLPipeline_h +#define hifi_gpu_gl_GLPipeline_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLPipeline : public GPUObject { +public: + static GLPipeline* sync(const Pipeline& pipeline); + + GLShader* _program { nullptr }; + GLState* _state { nullptr }; +}; + +} } + + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLQuery.h b/libraries/gpu-gl/src/gpu/gl/GLQuery.h new file mode 100644 index 0000000000..93f5ab20b3 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLQuery.h @@ -0,0 +1,63 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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_gpu_gl_GLQuery_h +#define hifi_gpu_gl_GLQuery_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLQuery : public GLObject { + using Parent = gpu::gl::GLObject; +public: + template + static GLQueryType* sync(const Query& query) { + GLQueryType* object = Backend::getGPUObject(query); + + // need to have a gpu object? + if (!object) { + // All is green, assign the gpuobject to the Query + object = new GLQueryType(query); + (void)CHECK_GL_ERROR(); + Backend::setGPUObject(query, object); + } + + return object; + } + + template + static GLuint getId(const QueryPointer& query) { + if (!query) { + return 0; + } + + GLQuery* object = sync(*query); + if (!object) { + return 0; + } + + return object->_endqo; + } + + const GLuint& _endqo = { _id }; + const GLuint _beginqo = { 0 }; + GLuint64 _result { (GLuint64)-1 }; + +protected: + GLQuery(const Query& query, GLuint endId, GLuint beginId) : Parent(query, endId), _beginqo(beginId){} + ~GLQuery() { + if (_id) { + GLuint ids[2] = { _endqo, _beginqo }; + glDeleteQueries(2, ids); + } + } +}; + +} } + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLShader.cpp b/libraries/gpu-gl/src/gpu/gl/GLShader.cpp new file mode 100644 index 0000000000..8dc3854b41 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLShader.cpp @@ -0,0 +1,187 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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 "GLShader.h" +#include "GLBackend.h" + +using namespace gpu; +using namespace gpu::gl; + +GLShader::GLShader() { +} + +GLShader::~GLShader() { + for (auto& so : _shaderObjects) { + if (so.glshader != 0) { + glDeleteShader(so.glshader); + } + if (so.glprogram != 0) { + glDeleteProgram(so.glprogram); + } + } +} + +// GLSL version +static const std::string glslVersion { + "#version 410 core" +}; + +// Shader domain +static const size_t NUM_SHADER_DOMAINS = 3; + +// GL Shader type enums +// Must match the order of type specified in gpu::Shader::Type +static const std::array SHADER_DOMAINS { { + GL_VERTEX_SHADER, + GL_FRAGMENT_SHADER, + GL_GEOMETRY_SHADER, +} }; + +// Domain specific defines +// Must match the order of type specified in gpu::Shader::Type +static const std::array DOMAIN_DEFINES { { + "#define GPU_VERTEX_SHADER", + "#define GPU_PIXEL_SHADER", + "#define GPU_GEOMETRY_SHADER", +} }; + +// Versions specific of the shader +static const std::array VERSION_DEFINES { { + "" +} }; + +GLShader* compileBackendShader(const Shader& shader) { + // Any GLSLprogram ? normally yes... + const std::string& shaderSource = shader.getSource().getCode(); + GLenum shaderDomain = SHADER_DOMAINS[shader.getType()]; + GLShader::ShaderObjects shaderObjects; + + for (int version = 0; version < GLShader::NumVersions; version++) { + auto& shaderObject = shaderObjects[version]; + + std::string shaderDefines = glslVersion + "\n" + DOMAIN_DEFINES[shader.getType()] + "\n" + VERSION_DEFINES[version]; + + bool result = compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, shaderObject.glprogram); + if (!result) { + return nullptr; + } + } + + // So far so good, the shader is created successfully + GLShader* object = new GLShader(); + object->_shaderObjects = shaderObjects; + + return object; +} + +GLShader* compileBackendProgram(const Shader& program) { + if (!program.isProgram()) { + return nullptr; + } + + GLShader::ShaderObjects programObjects; + + for (int version = 0; version < GLShader::NumVersions; version++) { + auto& programObject = programObjects[version]; + + // Let's go through every shaders and make sure they are ready to go + std::vector< GLuint > shaderGLObjects; + for (auto subShader : program.getShaders()) { + auto object = GLShader::sync(*subShader); + if (object) { + shaderGLObjects.push_back(object->_shaderObjects[version].glshader); + } else { + qCDebug(gpugllogging) << "GLShader::compileBackendProgram - One of the shaders of the program is not compiled?"; + return nullptr; + } + } + + GLuint glprogram = compileProgram(shaderGLObjects); + if (glprogram == 0) { + return nullptr; + } + + programObject.glprogram = glprogram; + + makeProgramBindings(programObject); + } + + // So far so good, the program versions have all been created successfully + GLShader* object = new GLShader(); + object->_shaderObjects = programObjects; + + return object; +} + +GLShader* GLShader::sync(const Shader& shader) { + GLShader* object = Backend::getGPUObject(shader); + + // If GPU object already created then good + if (object) { + return object; + } + // need to have a gpu object? + if (shader.isProgram()) { + GLShader* tempObject = compileBackendProgram(shader); + if (tempObject) { + object = tempObject; + Backend::setGPUObject(shader, object); + } + } else if (shader.isDomain()) { + GLShader* tempObject = compileBackendShader(shader); + if (tempObject) { + object = tempObject; + Backend::setGPUObject(shader, object); + } + } + + return object; +} + +bool GLShader::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings) { + + // First make sure the Shader has been compiled + GLShader* object = sync(shader); + if (!object) { + return false; + } + + // Apply bindings to all program versions and generate list of slots from default version + for (int version = 0; version < GLShader::NumVersions; version++) { + auto& shaderObject = object->_shaderObjects[version]; + if (shaderObject.glprogram) { + Shader::SlotSet buffers; + makeUniformBlockSlots(shaderObject.glprogram, slotBindings, buffers); + + Shader::SlotSet uniforms; + Shader::SlotSet textures; + Shader::SlotSet samplers; + makeUniformSlots(shaderObject.glprogram, slotBindings, uniforms, textures, samplers); + + Shader::SlotSet inputs; + makeInputSlots(shaderObject.glprogram, slotBindings, inputs); + + Shader::SlotSet outputs; + makeOutputSlots(shaderObject.glprogram, slotBindings, outputs); + + // Define the public slots only from the default version + if (version == 0) { + shader.defineSlots(uniforms, buffers, textures, samplers, inputs, outputs); + } else { + GLShader::UniformMapping mapping; + for (auto srcUniform : shader.getUniforms()) { + mapping[srcUniform._location] = uniforms.findLocation(srcUniform._name); + } + object->_uniformMappings.push_back(mapping); + } + } + } + + + return true; +} + diff --git a/libraries/gpu-gl/src/gpu/gl/GLShader.h b/libraries/gpu-gl/src/gpu/gl/GLShader.h new file mode 100644 index 0000000000..ca583e6d74 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLShader.h @@ -0,0 +1,52 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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_gpu_gl_GLShader_h +#define hifi_gpu_gl_GLShader_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLShader : public GPUObject { +public: + static GLShader* sync(const Shader& shader); + static bool makeProgram(Shader& shader, const Shader::BindingSet& slotBindings); + + enum Version { + Mono = 0, + NumVersions + }; + + using ShaderObject = gpu::gl::ShaderObject; + using ShaderObjects = std::array< ShaderObject, NumVersions >; + + using UniformMapping = std::map; + using UniformMappingVersions = std::vector; + + GLShader(); + ~GLShader(); + + ShaderObjects _shaderObjects; + UniformMappingVersions _uniformMappings; + + GLuint getProgram(Version version = Mono) const { + return _shaderObjects[version].glprogram; + } + + GLint getUniformLocation(GLint srcLoc, Version version = Mono) { + // THIS will be used in the future PR as we grow the number of versions + // return _uniformMappings[version][srcLoc]; + return srcLoc; + } + +}; + +} } + + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp b/libraries/gpu-gl/src/gpu/gl/GLShared.cpp similarity index 56% rename from libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp rename to libraries/gpu-gl/src/gpu/gl/GLShared.cpp index 8ebf7751d1..8f234ca6b4 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.cpp @@ -1,34 +1,649 @@ // -// GLBackendShader.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 2/28/2015. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2016/05/14 +// Copyright 2013-2016 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 "GLBackend.h" -#include "GLBackendShared.h" +#include "GLShared.h" -using namespace gpu; -using namespace gpu::gl; +#include -GLBackend::GLShader::GLShader() -{ -} +#include +#include +#include -GLBackend::GLShader::~GLShader() { - for (auto& so : _shaderObjects) { - if (so.glshader != 0) { - glDeleteShader(so.glshader); - } - if (so.glprogram != 0) { - glDeleteProgram(so.glprogram); +Q_LOGGING_CATEGORY(gpugllogging, "hifi.gpu.gl") + +namespace gpu { namespace gl { + +bool checkGLError(const char* name) { + GLenum error = glGetError(); + if (!error) { + return false; + } else { + switch (error) { + case GL_INVALID_ENUM: + qCDebug(gpugllogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag."; + break; + case GL_INVALID_VALUE: + qCDebug(gpugllogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag"; + break; + case GL_INVALID_OPERATION: + qCDebug(gpugllogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag.."; + break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + qCDebug(gpugllogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag."; + break; + case GL_OUT_OF_MEMORY: + qCDebug(gpugllogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded."; + break; + case GL_STACK_UNDERFLOW: + qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to underflow."; + break; + case GL_STACK_OVERFLOW: + qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to overflow."; + break; } + return true; } } +bool checkGLErrorDebug(const char* name) { +#ifdef DEBUG + return checkGLError(name); +#else + Q_UNUSED(name); + return false; +#endif +} + +gpu::Size getDedicatedMemory() { + static Size dedicatedMemory { 0 }; + static std::once_flag once; + std::call_once(once, [&] { +#ifdef Q_OS_WIN + if (!dedicatedMemory && wglGetGPUIDsAMD && wglGetGPUInfoAMD) { + UINT maxCount = wglGetGPUIDsAMD(0, 0); + std::vector ids; + ids.resize(maxCount); + wglGetGPUIDsAMD(maxCount, &ids[0]); + GLuint memTotal; + wglGetGPUInfoAMD(ids[0], WGL_GPU_RAM_AMD, GL_UNSIGNED_INT, sizeof(GLuint), &memTotal); + dedicatedMemory = MB_TO_BYTES(memTotal); + } +#endif + + if (!dedicatedMemory) { + GLint atiGpuMemory[4]; + // not really total memory, but close enough if called early enough in the application lifecycle + glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, atiGpuMemory); + if (GL_NO_ERROR == glGetError()) { + dedicatedMemory = KB_TO_BYTES(atiGpuMemory[0]); + } + } + + if (!dedicatedMemory) { + GLint nvGpuMemory { 0 }; + glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &nvGpuMemory); + if (GL_NO_ERROR == glGetError()) { + dedicatedMemory = KB_TO_BYTES(nvGpuMemory); + } + } + + if (!dedicatedMemory) { + auto gpuIdent = GPUIdent::getInstance(); + if (gpuIdent && gpuIdent->isValid()) { + dedicatedMemory = MB_TO_BYTES(gpuIdent->getMemory()); + } + } + }); + + return dedicatedMemory; +} + + + + +ComparisonFunction comparisonFuncFromGL(GLenum func) { + if (func == GL_NEVER) { + return NEVER; + } else if (func == GL_LESS) { + return LESS; + } else if (func == GL_EQUAL) { + return EQUAL; + } else if (func == GL_LEQUAL) { + return LESS_EQUAL; + } else if (func == GL_GREATER) { + return GREATER; + } else if (func == GL_NOTEQUAL) { + return NOT_EQUAL; + } else if (func == GL_GEQUAL) { + return GREATER_EQUAL; + } else if (func == GL_ALWAYS) { + return ALWAYS; + } + + return ALWAYS; +} + +State::StencilOp stencilOpFromGL(GLenum stencilOp) { + if (stencilOp == GL_KEEP) { + return State::STENCIL_OP_KEEP; + } else if (stencilOp == GL_ZERO) { + return State::STENCIL_OP_ZERO; + } else if (stencilOp == GL_REPLACE) { + return State::STENCIL_OP_REPLACE; + } else if (stencilOp == GL_INCR_WRAP) { + return State::STENCIL_OP_INCR_SAT; + } else if (stencilOp == GL_DECR_WRAP) { + return State::STENCIL_OP_DECR_SAT; + } else if (stencilOp == GL_INVERT) { + return State::STENCIL_OP_INVERT; + } else if (stencilOp == GL_INCR) { + return State::STENCIL_OP_INCR; + } else if (stencilOp == GL_DECR) { + return State::STENCIL_OP_DECR; + } + + return State::STENCIL_OP_KEEP; +} + +State::BlendOp blendOpFromGL(GLenum blendOp) { + if (blendOp == GL_FUNC_ADD) { + return State::BLEND_OP_ADD; + } else if (blendOp == GL_FUNC_SUBTRACT) { + return State::BLEND_OP_SUBTRACT; + } else if (blendOp == GL_FUNC_REVERSE_SUBTRACT) { + return State::BLEND_OP_REV_SUBTRACT; + } else if (blendOp == GL_MIN) { + return State::BLEND_OP_MIN; + } else if (blendOp == GL_MAX) { + return State::BLEND_OP_MAX; + } + + return State::BLEND_OP_ADD; +} + +State::BlendArg blendArgFromGL(GLenum blendArg) { + if (blendArg == GL_ZERO) { + return State::ZERO; + } else if (blendArg == GL_ONE) { + return State::ONE; + } else if (blendArg == GL_SRC_COLOR) { + return State::SRC_COLOR; + } else if (blendArg == GL_ONE_MINUS_SRC_COLOR) { + return State::INV_SRC_COLOR; + } else if (blendArg == GL_DST_COLOR) { + return State::DEST_COLOR; + } else if (blendArg == GL_ONE_MINUS_DST_COLOR) { + return State::INV_DEST_COLOR; + } else if (blendArg == GL_SRC_ALPHA) { + return State::SRC_ALPHA; + } else if (blendArg == GL_ONE_MINUS_SRC_ALPHA) { + return State::INV_SRC_ALPHA; + } else if (blendArg == GL_DST_ALPHA) { + return State::DEST_ALPHA; + } else if (blendArg == GL_ONE_MINUS_DST_ALPHA) { + return State::INV_DEST_ALPHA; + } else if (blendArg == GL_CONSTANT_COLOR) { + return State::FACTOR_COLOR; + } else if (blendArg == GL_ONE_MINUS_CONSTANT_COLOR) { + return State::INV_FACTOR_COLOR; + } else if (blendArg == GL_CONSTANT_ALPHA) { + return State::FACTOR_ALPHA; + } else if (blendArg == GL_ONE_MINUS_CONSTANT_ALPHA) { + return State::INV_FACTOR_ALPHA; + } + + return State::ONE; +} + +void getCurrentGLState(State::Data& state) { + { + GLint modes[2]; + glGetIntegerv(GL_POLYGON_MODE, modes); + if (modes[0] == GL_FILL) { + state.fillMode = State::FILL_FACE; + } else { + if (modes[0] == GL_LINE) { + state.fillMode = State::FILL_LINE; + } else { + state.fillMode = State::FILL_POINT; + } + } + } + { + if (glIsEnabled(GL_CULL_FACE)) { + GLint mode; + glGetIntegerv(GL_CULL_FACE_MODE, &mode); + state.cullMode = (mode == GL_FRONT ? State::CULL_FRONT : State::CULL_BACK); + } else { + state.cullMode = State::CULL_NONE; + } + } + { + GLint winding; + glGetIntegerv(GL_FRONT_FACE, &winding); + state.frontFaceClockwise = (winding == GL_CW); + state.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP); + state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); + state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE); + state.antialisedLineEnable = glIsEnabled(GL_LINE_SMOOTH); + } + { + if (glIsEnabled(GL_POLYGON_OFFSET_FILL)) { + glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &state.depthBiasSlopeScale); + glGetFloatv(GL_POLYGON_OFFSET_UNITS, &state.depthBias); + } + } + { + GLboolean isEnabled = glIsEnabled(GL_DEPTH_TEST); + GLboolean writeMask; + glGetBooleanv(GL_DEPTH_WRITEMASK, &writeMask); + GLint func; + glGetIntegerv(GL_DEPTH_FUNC, &func); + + state.depthTest = State::DepthTest(isEnabled, writeMask, comparisonFuncFromGL(func)); + } + { + GLboolean isEnabled = glIsEnabled(GL_STENCIL_TEST); + + GLint frontWriteMask; + GLint frontReadMask; + GLint frontRef; + GLint frontFail; + GLint frontDepthFail; + GLint frontPass; + GLint frontFunc; + glGetIntegerv(GL_STENCIL_WRITEMASK, &frontWriteMask); + glGetIntegerv(GL_STENCIL_VALUE_MASK, &frontReadMask); + glGetIntegerv(GL_STENCIL_REF, &frontRef); + glGetIntegerv(GL_STENCIL_FAIL, &frontFail); + glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &frontDepthFail); + glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &frontPass); + glGetIntegerv(GL_STENCIL_FUNC, &frontFunc); + + GLint backWriteMask; + GLint backReadMask; + GLint backRef; + GLint backFail; + GLint backDepthFail; + GLint backPass; + GLint backFunc; + glGetIntegerv(GL_STENCIL_BACK_WRITEMASK, &backWriteMask); + glGetIntegerv(GL_STENCIL_BACK_VALUE_MASK, &backReadMask); + glGetIntegerv(GL_STENCIL_BACK_REF, &backRef); + glGetIntegerv(GL_STENCIL_BACK_FAIL, &backFail); + glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_FAIL, &backDepthFail); + glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_PASS, &backPass); + glGetIntegerv(GL_STENCIL_BACK_FUNC, &backFunc); + + state.stencilActivation = State::StencilActivation(isEnabled, frontWriteMask, backWriteMask); + state.stencilTestFront = State::StencilTest(frontRef, frontReadMask, comparisonFuncFromGL(frontFunc), stencilOpFromGL(frontFail), stencilOpFromGL(frontDepthFail), stencilOpFromGL(frontPass)); + state.stencilTestBack = State::StencilTest(backRef, backReadMask, comparisonFuncFromGL(backFunc), stencilOpFromGL(backFail), stencilOpFromGL(backDepthFail), stencilOpFromGL(backPass)); + } + { + GLint mask = 0xFFFFFFFF; + if (glIsEnabled(GL_SAMPLE_MASK)) { + glGetIntegerv(GL_SAMPLE_MASK, &mask); + state.sampleMask = mask; + } + state.sampleMask = mask; + } + { + state.alphaToCoverageEnable = glIsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE); + } + { + GLboolean isEnabled = glIsEnabled(GL_BLEND); + GLint srcRGB; + GLint srcA; + GLint dstRGB; + GLint dstA; + glGetIntegerv(GL_BLEND_SRC_RGB, &srcRGB); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &srcA); + glGetIntegerv(GL_BLEND_DST_RGB, &dstRGB); + glGetIntegerv(GL_BLEND_DST_ALPHA, &dstA); + + GLint opRGB; + GLint opA; + glGetIntegerv(GL_BLEND_EQUATION_RGB, &opRGB); + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &opA); + + state.blendFunction = State::BlendFunction(isEnabled, + blendArgFromGL(srcRGB), blendOpFromGL(opRGB), blendArgFromGL(dstRGB), + blendArgFromGL(srcA), blendOpFromGL(opA), blendArgFromGL(dstA)); + } + { + GLboolean mask[4]; + glGetBooleanv(GL_COLOR_WRITEMASK, mask); + state.colorWriteMask = (mask[0] ? State::WRITE_RED : 0) + | (mask[1] ? State::WRITE_GREEN : 0) + | (mask[2] ? State::WRITE_BLUE : 0) + | (mask[3] ? State::WRITE_ALPHA : 0); + } + + (void)CHECK_GL_ERROR(); +} + + +class ElementResource { +public: + gpu::Element _element; + uint16 _resource; + + ElementResource(Element&& elem, uint16 resource) : _element(elem), _resource(resource) {} +}; + +ElementResource getFormatFromGLUniform(GLenum gltype) { + switch (gltype) { + case GL_FLOAT: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); + /* + case GL_DOUBLE: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_DOUBLE_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_DOUBLE_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_DOUBLE_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); + */ + case GL_INT: return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC2: return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC3: return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC4: return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER); + + case GL_UNSIGNED_INT: return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER); +#if defined(Q_OS_WIN) + case GL_UNSIGNED_INT_VEC2: return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER); + case GL_UNSIGNED_INT_VEC3: return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER); + case GL_UNSIGNED_INT_VEC4: return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER); +#endif + + case GL_BOOL: return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC2: return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC3: return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC4: return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER); + + + case GL_FLOAT_MAT2: return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_MAT3: return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_MAT4: return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER); + + /* {GL_FLOAT_MAT2x3 mat2x3}, + {GL_FLOAT_MAT2x4 mat2x4}, + {GL_FLOAT_MAT3x2 mat3x2}, + {GL_FLOAT_MAT3x4 mat3x4}, + {GL_FLOAT_MAT4x2 mat4x2}, + {GL_FLOAT_MAT4x3 mat4x3}, + {GL_DOUBLE_MAT2 dmat2}, + {GL_DOUBLE_MAT3 dmat3}, + {GL_DOUBLE_MAT4 dmat4}, + {GL_DOUBLE_MAT2x3 dmat2x3}, + {GL_DOUBLE_MAT2x4 dmat2x4}, + {GL_DOUBLE_MAT3x2 dmat3x2}, + {GL_DOUBLE_MAT3x4 dmat3x4}, + {GL_DOUBLE_MAT4x2 dmat4x2}, + {GL_DOUBLE_MAT4x3 dmat4x3}, + */ + + case GL_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D); + case GL_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D); + + case GL_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D); + case GL_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE); + +#if defined(Q_OS_WIN) + case GL_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY); + case GL_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); +#endif + + case GL_SAMPLER_2D_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D); +#if defined(Q_OS_WIN) + case GL_SAMPLER_CUBE_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE); + + case GL_SAMPLER_2D_ARRAY_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY); +#endif + + // {GL_SAMPLER_1D_SHADOW sampler1DShadow}, + // {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow}, + + // {GL_SAMPLER_BUFFER samplerBuffer}, + // {GL_SAMPLER_2D_RECT sampler2DRect}, + // {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow}, + +#if defined(Q_OS_WIN) + case GL_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D); + case GL_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D); + case GL_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D); + case GL_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE); + + case GL_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); + case GL_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); + + // {GL_INT_SAMPLER_BUFFER isamplerBuffer}, + // {GL_INT_SAMPLER_2D_RECT isampler2DRect}, + + case GL_UNSIGNED_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D); + case GL_UNSIGNED_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D); + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_UNSIGNED_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D); + case GL_UNSIGNED_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE); + + case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); + case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); +#endif + // {GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer}, + // {GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect}, + /* + {GL_IMAGE_1D image1D}, + {GL_IMAGE_2D image2D}, + {GL_IMAGE_3D image3D}, + {GL_IMAGE_2D_RECT image2DRect}, + {GL_IMAGE_CUBE imageCube}, + {GL_IMAGE_BUFFER imageBuffer}, + {GL_IMAGE_1D_ARRAY image1DArray}, + {GL_IMAGE_2D_ARRAY image2DArray}, + {GL_IMAGE_2D_MULTISAMPLE image2DMS}, + {GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray}, + {GL_INT_IMAGE_1D iimage1D}, + {GL_INT_IMAGE_2D iimage2D}, + {GL_INT_IMAGE_3D iimage3D}, + {GL_INT_IMAGE_2D_RECT iimage2DRect}, + {GL_INT_IMAGE_CUBE iimageCube}, + {GL_INT_IMAGE_BUFFER iimageBuffer}, + {GL_INT_IMAGE_1D_ARRAY iimage1DArray}, + {GL_INT_IMAGE_2D_ARRAY iimage2DArray}, + {GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS}, + {GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray}, + {GL_UNSIGNED_INT_IMAGE_1D uimage1D}, + {GL_UNSIGNED_INT_IMAGE_2D uimage2D}, + {GL_UNSIGNED_INT_IMAGE_3D uimage3D}, + {GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect}, + {GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot + + {GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer}, + {GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray}, + {GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray}, + {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, + {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, + {GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} + */ + default: + return ElementResource(Element(), Resource::BUFFER); + } + +}; + +int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, + Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers) { + GLint uniformsCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_UNIFORMS, &uniformsCount); + + for (int i = 0; i < uniformsCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLenum type = 0; + glGetActiveUniform(glprogram, i, NAME_LENGTH, &length, &size, &type, name); + GLint location = glGetUniformLocation(glprogram, name); + const GLint INVALID_UNIFORM_LOCATION = -1; + + // Try to make sense of the gltype + auto elementResource = getFormatFromGLUniform(type); + + // The uniform as a standard var type + if (location != INVALID_UNIFORM_LOCATION) { + // Let's make sure the name doesn't contains an array element + std::string sname(name); + auto foundBracket = sname.find_first_of('['); + if (foundBracket != std::string::npos) { + // std::string arrayname = sname.substr(0, foundBracket); + + if (sname[foundBracket + 1] == '0') { + sname = sname.substr(0, foundBracket); + } else { + // skip this uniform since it's not the first element of an array + continue; + } + } + + if (elementResource._resource == Resource::BUFFER) { + uniforms.insert(Shader::Slot(sname, location, elementResource._element, elementResource._resource)); + } else { + // For texture/Sampler, the location is the actual binding value + GLint binding = -1; + glGetUniformiv(glprogram, location, &binding); + + auto requestedBinding = slotBindings.find(std::string(sname)); + if (requestedBinding != slotBindings.end()) { + if (binding != (*requestedBinding)._location) { + binding = (*requestedBinding)._location; + glProgramUniform1i(glprogram, location, binding); + } + } + + textures.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); + samplers.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); + } + } + } + + return uniformsCount; +} + +const GLint UNUSED_SLOT = -1; +bool isUnusedSlot(GLint binding) { + return (binding == UNUSED_SLOT); +} + +int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers) { + GLint buffersCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_UNIFORM_BLOCKS, &buffersCount); + + // fast exit + if (buffersCount == 0) { + return 0; + } + + GLint maxNumUniformBufferSlots = 0; + glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxNumUniformBufferSlots); + std::vector uniformBufferSlotMap(maxNumUniformBufferSlots, -1); + + for (int i = 0; i < buffersCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLint binding = -1; + + glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_NAME_LENGTH, &length); + glGetActiveUniformBlockName(glprogram, i, NAME_LENGTH, &length, name); + glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_BINDING, &binding); + glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_DATA_SIZE, &size); + + GLuint blockIndex = glGetUniformBlockIndex(glprogram, name); + + // CHeck if there is a requested binding for this block + auto requestedBinding = slotBindings.find(std::string(name)); + if (requestedBinding != slotBindings.end()) { + // If yes force it + if (binding != (*requestedBinding)._location) { + binding = (*requestedBinding)._location; + glUniformBlockBinding(glprogram, blockIndex, binding); + } + } else if (binding == 0) { + // If no binding was assigned then just do it finding a free slot + auto slotIt = std::find_if(uniformBufferSlotMap.begin(), uniformBufferSlotMap.end(), isUnusedSlot); + if (slotIt != uniformBufferSlotMap.end()) { + binding = slotIt - uniformBufferSlotMap.begin(); + glUniformBlockBinding(glprogram, blockIndex, binding); + } else { + // This should neve happen, an active ubo cannot find an available slot among the max available?! + binding = -1; + } + } + // If binding is valid record it + if (binding >= 0) { + uniformBufferSlotMap[binding] = blockIndex; + } + + Element element(SCALAR, gpu::UINT32, gpu::UNIFORM_BUFFER); + buffers.insert(Shader::Slot(name, binding, element, Resource::BUFFER)); + } + return buffersCount; +} + +int makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs) { + GLint inputsCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_ATTRIBUTES, &inputsCount); + + for (int i = 0; i < inputsCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLenum type = 0; + glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); + + GLint binding = glGetAttribLocation(glprogram, name); + + auto elementResource = getFormatFromGLUniform(type); + inputs.insert(Shader::Slot(name, binding, elementResource._element, -1)); + } + + return inputsCount; +} + +int makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs) { + /* GLint outputsCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_, &outputsCount); + + for (int i = 0; i < inputsCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLenum type = 0; + glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); + + auto element = getFormatFromGLUniform(type); + outputs.insert(Shader::Slot(name, i, element)); + } + */ + return 0; //inputsCount; +} + + bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const std::string& defines, GLuint &shaderObject, GLuint &programObject) { if (shaderSource.empty()) { qCDebug(gpugllogging) << "GLShader::compileShader - no GLSL shader source code ? so failed to create"; @@ -56,11 +671,14 @@ bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const s // if compilation fails if (!compiled) { + // save the source code to a temp file so we can debug easily - /* std::ofstream filestream; + /* + std::ofstream filestream; filestream.open("debugshader.glsl"); if (filestream.is_open()) { - filestream << shaderSource->source; + filestream << srcstr[0]; + filestream << srcstr[1]; filestream.close(); } */ @@ -206,7 +824,7 @@ GLuint compileProgram(const std::vector& glshaders) { } -void makeProgramBindings(GLBackend::GLShader::ShaderObject& shaderObject) { +void makeProgramBindings(ShaderObject& shaderObject) { if (!shaderObject.glprogram) { return; } @@ -294,475 +912,9 @@ void makeProgramBindings(GLBackend::GLShader::ShaderObject& shaderObject) { (void)CHECK_GL_ERROR(); } -GLBackend::GLShader* compileBackendShader(const Shader& shader) { - // Any GLSLprogram ? normally yes... - const std::string& shaderSource = shader.getSource().getCode(); - // GLSL version - const std::string glslVersion = { - "#version 410 core" - }; +} } - // Shader domain - const int NUM_SHADER_DOMAINS = 2; - const GLenum SHADER_DOMAINS[NUM_SHADER_DOMAINS] = { - GL_VERTEX_SHADER, - GL_FRAGMENT_SHADER - }; - GLenum shaderDomain = SHADER_DOMAINS[shader.getType()]; +using namespace gpu; - // Domain specific defines - const std::string domainDefines[NUM_SHADER_DOMAINS] = { - "#define GPU_VERTEX_SHADER", - "#define GPU_PIXEL_SHADER" - }; - - // Versions specific of the shader - const std::string versionDefines[GLBackend::GLShader::NumVersions] = { - "" - }; - - GLBackend::GLShader::ShaderObjects shaderObjects; - - for (int version = 0; version < GLBackend::GLShader::NumVersions; version++) { - auto& shaderObject = shaderObjects[version]; - - std::string shaderDefines = glslVersion + "\n" + domainDefines[shader.getType()] + "\n" + versionDefines[version]; - - bool result = compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, shaderObject.glprogram); - if (!result) { - return nullptr; - } - } - - // So far so good, the shader is created successfully - GLBackend::GLShader* object = new GLBackend::GLShader(); - object->_shaderObjects = shaderObjects; - - return object; -} - -GLBackend::GLShader* compileBackendProgram(const Shader& program) { - if (!program.isProgram()) { - return nullptr; - } - - GLBackend::GLShader::ShaderObjects programObjects; - - for (int version = 0; version < GLBackend::GLShader::NumVersions; version++) { - auto& programObject = programObjects[version]; - - // Let's go through every shaders and make sure they are ready to go - std::vector< GLuint > shaderGLObjects; - for (auto subShader : program.getShaders()) { - auto object = GLBackend::syncGPUObject(*subShader); - if (object) { - shaderGLObjects.push_back(object->_shaderObjects[version].glshader); - } else { - qCDebug(gpugllogging) << "GLShader::compileBackendProgram - One of the shaders of the program is not compiled?"; - return nullptr; - } - } - - GLuint glprogram = compileProgram(shaderGLObjects); - if (glprogram == 0) { - return nullptr; - } - - programObject.glprogram = glprogram; - - makeProgramBindings(programObject); - } - - // So far so good, the program versions have all been created successfully - GLBackend::GLShader* object = new GLBackend::GLShader(); - object->_shaderObjects = programObjects; - - return object; -} - -GLBackend::GLShader* GLBackend::syncGPUObject(const Shader& shader) { - GLShader* object = Backend::getGPUObject(shader); - - // If GPU object already created then good - if (object) { - return object; - } - // need to have a gpu object? - if (shader.isProgram()) { - GLShader* tempObject = compileBackendProgram(shader); - if (tempObject) { - object = tempObject; - Backend::setGPUObject(shader, object); - } - } else if (shader.isDomain()) { - GLShader* tempObject = compileBackendShader(shader); - if (tempObject) { - object = tempObject; - Backend::setGPUObject(shader, object); - } - } - - return object; -} - -class ElementResource { -public: - gpu::Element _element; - uint16 _resource; - - ElementResource(Element&& elem, uint16 resource) : _element(elem), _resource(resource) {} -}; - -ElementResource getFormatFromGLUniform(GLenum gltype) { - switch (gltype) { - case GL_FLOAT: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); -/* - case GL_DOUBLE: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); -*/ - case GL_INT: return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC2: return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC3: return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC4: return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER); - - case GL_UNSIGNED_INT: return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER); -#if defined(Q_OS_WIN) - case GL_UNSIGNED_INT_VEC2: return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT_VEC3: return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT_VEC4: return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER); -#endif - - case GL_BOOL: return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC2: return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC3: return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC4: return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER); - - - case GL_FLOAT_MAT2: return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_MAT3: return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_MAT4: return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER); - -/* {GL_FLOAT_MAT2x3 mat2x3}, - {GL_FLOAT_MAT2x4 mat2x4}, - {GL_FLOAT_MAT3x2 mat3x2}, - {GL_FLOAT_MAT3x4 mat3x4}, - {GL_FLOAT_MAT4x2 mat4x2}, - {GL_FLOAT_MAT4x3 mat4x3}, - {GL_DOUBLE_MAT2 dmat2}, - {GL_DOUBLE_MAT3 dmat3}, - {GL_DOUBLE_MAT4 dmat4}, - {GL_DOUBLE_MAT2x3 dmat2x3}, - {GL_DOUBLE_MAT2x4 dmat2x4}, - {GL_DOUBLE_MAT3x2 dmat3x2}, - {GL_DOUBLE_MAT3x4 dmat3x4}, - {GL_DOUBLE_MAT4x2 dmat4x2}, - {GL_DOUBLE_MAT4x3 dmat4x3}, - */ - - case GL_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D); - case GL_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D); - - case GL_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D); - case GL_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE); - -#if defined(Q_OS_WIN) - case GL_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); -#endif - - case GL_SAMPLER_2D_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D); -#if defined(Q_OS_WIN) - case GL_SAMPLER_CUBE_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE); - - case GL_SAMPLER_2D_ARRAY_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY); -#endif - -// {GL_SAMPLER_1D_SHADOW sampler1DShadow}, - // {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow}, - -// {GL_SAMPLER_BUFFER samplerBuffer}, -// {GL_SAMPLER_2D_RECT sampler2DRect}, - // {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow}, - -#if defined(Q_OS_WIN) - case GL_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D); - case GL_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D); - case GL_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D); - case GL_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE); - - case GL_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); - - // {GL_INT_SAMPLER_BUFFER isamplerBuffer}, - // {GL_INT_SAMPLER_2D_RECT isampler2DRect}, - - case GL_UNSIGNED_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D); - case GL_UNSIGNED_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D); - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_UNSIGNED_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D); - case GL_UNSIGNED_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE); - - case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); -#endif -// {GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer}, -// {GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect}, -/* - {GL_IMAGE_1D image1D}, - {GL_IMAGE_2D image2D}, - {GL_IMAGE_3D image3D}, - {GL_IMAGE_2D_RECT image2DRect}, - {GL_IMAGE_CUBE imageCube}, - {GL_IMAGE_BUFFER imageBuffer}, - {GL_IMAGE_1D_ARRAY image1DArray}, - {GL_IMAGE_2D_ARRAY image2DArray}, - {GL_IMAGE_2D_MULTISAMPLE image2DMS}, - {GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray}, - {GL_INT_IMAGE_1D iimage1D}, - {GL_INT_IMAGE_2D iimage2D}, - {GL_INT_IMAGE_3D iimage3D}, - {GL_INT_IMAGE_2D_RECT iimage2DRect}, - {GL_INT_IMAGE_CUBE iimageCube}, - {GL_INT_IMAGE_BUFFER iimageBuffer}, - {GL_INT_IMAGE_1D_ARRAY iimage1DArray}, - {GL_INT_IMAGE_2D_ARRAY iimage2DArray}, - {GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS}, - {GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray}, - {GL_UNSIGNED_INT_IMAGE_1D uimage1D}, - {GL_UNSIGNED_INT_IMAGE_2D uimage2D}, - {GL_UNSIGNED_INT_IMAGE_3D uimage3D}, - {GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect}, - {GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot - - {GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer}, - {GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray}, - {GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray}, - {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, - {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, - {GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} -*/ - default: - return ElementResource(Element(), Resource::BUFFER); - } - -}; - - -int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, - Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers) { - GLint uniformsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_UNIFORMS, &uniformsCount); - - for (int i = 0; i < uniformsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveUniform(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - GLint location = glGetUniformLocation(glprogram, name); - const GLint INVALID_UNIFORM_LOCATION = -1; - - // Try to make sense of the gltype - auto elementResource = getFormatFromGLUniform(type); - - // The uniform as a standard var type - if (location != INVALID_UNIFORM_LOCATION) { - // Let's make sure the name doesn't contains an array element - std::string sname(name); - auto foundBracket = sname.find_first_of('['); - if (foundBracket != std::string::npos) { - // std::string arrayname = sname.substr(0, foundBracket); - - if (sname[foundBracket + 1] == '0') { - sname = sname.substr(0, foundBracket); - } else { - // skip this uniform since it's not the first element of an array - continue; - } - } - - if (elementResource._resource == Resource::BUFFER) { - uniforms.insert(Shader::Slot(sname, location, elementResource._element, elementResource._resource)); - } else { - // For texture/Sampler, the location is the actual binding value - GLint binding = -1; - glGetUniformiv(glprogram, location, &binding); - - auto requestedBinding = slotBindings.find(std::string(sname)); - if (requestedBinding != slotBindings.end()) { - if (binding != (*requestedBinding)._location) { - binding = (*requestedBinding)._location; - glProgramUniform1i(glprogram, location, binding); - } - } - - textures.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - samplers.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - } - } - } - - return uniformsCount; -} - -const GLint UNUSED_SLOT = -1; -bool isUnusedSlot(GLint binding) { - return (binding == UNUSED_SLOT); -} - -int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers) { - GLint buffersCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_UNIFORM_BLOCKS, &buffersCount); - - // fast exit - if (buffersCount == 0) { - return 0; - } - - GLint maxNumUniformBufferSlots = 0; - glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxNumUniformBufferSlots); - std::vector uniformBufferSlotMap(maxNumUniformBufferSlots, -1); - - for (int i = 0; i < buffersCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLint binding = -1; - - glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_NAME_LENGTH, &length); - glGetActiveUniformBlockName(glprogram, i, NAME_LENGTH, &length, name); - glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_BINDING, &binding); - glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_DATA_SIZE, &size); - - GLuint blockIndex = glGetUniformBlockIndex(glprogram, name); - - // CHeck if there is a requested binding for this block - auto requestedBinding = slotBindings.find(std::string(name)); - if (requestedBinding != slotBindings.end()) { - // If yes force it - if (binding != (*requestedBinding)._location) { - binding = (*requestedBinding)._location; - glUniformBlockBinding(glprogram, blockIndex, binding); - } - } else if (binding == 0) { - // If no binding was assigned then just do it finding a free slot - auto slotIt = std::find_if(uniformBufferSlotMap.begin(), uniformBufferSlotMap.end(), isUnusedSlot); - if (slotIt != uniformBufferSlotMap.end()) { - binding = slotIt - uniformBufferSlotMap.begin(); - glUniformBlockBinding(glprogram, blockIndex, binding); - } else { - // This should neve happen, an active ubo cannot find an available slot among the max available?! - binding = -1; - } - } - // If binding is valid record it - if (binding >= 0) { - uniformBufferSlotMap[binding] = blockIndex; - } - - Element element(SCALAR, gpu::UINT32, gpu::UNIFORM_BUFFER); - buffers.insert(Shader::Slot(name, binding, element, Resource::BUFFER)); - } - return buffersCount; -} - -int makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs) { - GLint inputsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_ATTRIBUTES, &inputsCount); - - for (int i = 0; i < inputsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - - GLint binding = glGetAttribLocation(glprogram, name); - - auto elementResource = getFormatFromGLUniform(type); - inputs.insert(Shader::Slot(name, binding, elementResource._element, -1)); - } - - return inputsCount; -} - -int makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs) { - /* GLint outputsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_, &outputsCount); - - for (int i = 0; i < inputsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - - auto element = getFormatFromGLUniform(type); - outputs.insert(Shader::Slot(name, i, element)); - } - */ - return 0; //inputsCount; -} - -bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings) { - - // First make sure the Shader has been compiled - GLShader* object = GLBackend::syncGPUObject(shader); - if (!object) { - return false; - } - - // Apply bindings to all program versions and generate list of slots from default version - for (int version = 0; version < GLBackend::GLShader::NumVersions; version++) { - auto& shaderObject = object->_shaderObjects[version]; - if (shaderObject.glprogram) { - Shader::SlotSet buffers; - makeUniformBlockSlots(shaderObject.glprogram, slotBindings, buffers); - - Shader::SlotSet uniforms; - Shader::SlotSet textures; - Shader::SlotSet samplers; - makeUniformSlots(shaderObject.glprogram, slotBindings, uniforms, textures, samplers); - - Shader::SlotSet inputs; - makeInputSlots(shaderObject.glprogram, slotBindings, inputs); - - Shader::SlotSet outputs; - makeOutputSlots(shaderObject.glprogram, slotBindings, outputs); - - // Define the public slots only from the default version - if (version == 0) { - shader.defineSlots(uniforms, buffers, textures, samplers, inputs, outputs); - } else { - GLShader::UniformMapping mapping; - for (auto srcUniform : shader.getUniforms()) { - mapping[srcUniform._location] = uniforms.findLocation(srcUniform._name); - } - object->_uniformMappings.push_back(mapping); - } - } - } - - - return true; -} diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.h b/libraries/gpu-gl/src/gpu/gl/GLShared.h new file mode 100644 index 0000000000..3220eafef4 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.h @@ -0,0 +1,150 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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_gpu_GLShared_h +#define hifi_gpu_GLShared_h + +#include +#include +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(gpugllogging) + +namespace gpu { namespace gl { + +gpu::Size getDedicatedMemory(); +ComparisonFunction comparisonFuncFromGL(GLenum func); +State::StencilOp stencilOpFromGL(GLenum stencilOp); +State::BlendOp blendOpFromGL(GLenum blendOp); +State::BlendArg blendArgFromGL(GLenum blendArg); +void getCurrentGLState(State::Data& state); + +struct ShaderObject { + GLuint glshader { 0 }; + GLuint glprogram { 0 }; + GLint transformCameraSlot { -1 }; + GLint transformObjectSlot { -1 }; +}; + +int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, + Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers); +int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers); +int makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs); +int makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs); +bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const std::string& defines, GLuint &shaderObject, GLuint &programObject); +GLuint compileProgram(const std::vector& glshaders); +void makeProgramBindings(ShaderObject& shaderObject); + +enum GLSyncState { + // The object is currently undergoing no processing, although it's content + // may be out of date, or it's storage may be invalid relative to the + // owning GPU object + Idle, + // The object has been queued for transfer to the GPU + Pending, + // The object has been transferred to the GPU, but is awaiting + // any post transfer operations that may need to occur on the + // primary rendering thread + Transferred, +}; + +static const GLenum BLEND_OPS_TO_GL[State::NUM_BLEND_OPS] = { + GL_FUNC_ADD, + GL_FUNC_SUBTRACT, + GL_FUNC_REVERSE_SUBTRACT, + GL_MIN, + GL_MAX +}; + +static const GLenum BLEND_ARGS_TO_GL[State::NUM_BLEND_ARGS] = { + GL_ZERO, + GL_ONE, + GL_SRC_COLOR, + GL_ONE_MINUS_SRC_COLOR, + GL_SRC_ALPHA, + GL_ONE_MINUS_SRC_ALPHA, + GL_DST_ALPHA, + GL_ONE_MINUS_DST_ALPHA, + GL_DST_COLOR, + GL_ONE_MINUS_DST_COLOR, + GL_SRC_ALPHA_SATURATE, + GL_CONSTANT_COLOR, + GL_ONE_MINUS_CONSTANT_COLOR, + GL_CONSTANT_ALPHA, + GL_ONE_MINUS_CONSTANT_ALPHA, +}; + +static const GLenum COMPARISON_TO_GL[gpu::NUM_COMPARISON_FUNCS] = { + GL_NEVER, + GL_LESS, + GL_EQUAL, + GL_LEQUAL, + GL_GREATER, + GL_NOTEQUAL, + GL_GEQUAL, + GL_ALWAYS +}; + +static const GLenum PRIMITIVE_TO_GL[gpu::NUM_PRIMITIVES] = { + GL_POINTS, + GL_LINES, + GL_LINE_STRIP, + GL_TRIANGLES, + GL_TRIANGLE_STRIP, + GL_TRIANGLE_FAN, +}; + +static const GLenum ELEMENT_TYPE_TO_GL[gpu::NUM_TYPES] = { + GL_FLOAT, + GL_INT, + GL_UNSIGNED_INT, + GL_HALF_FLOAT, + GL_SHORT, + GL_UNSIGNED_SHORT, + GL_BYTE, + GL_UNSIGNED_BYTE, + // Normalized values + GL_INT, + GL_UNSIGNED_INT, + GL_SHORT, + GL_UNSIGNED_SHORT, + GL_BYTE, + GL_UNSIGNED_BYTE +}; + +bool checkGLError(const char* name = nullptr); +bool checkGLErrorDebug(const char* name = nullptr); + +template +struct GLObject : public GPUObject { +public: + GLObject(const GPUType& gpuObject, GLuint id) : _gpuObject(gpuObject), _id(id) {} + + virtual ~GLObject() { } + + const GPUType& _gpuObject; + const GLuint _id; +}; + +class GlBuffer; +class GLFramebuffer; +class GLPipeline; +class GLQuery; +class GLState; +class GLShader; +class GLTexture; + +} } // namespace gpu::gl + +#define CHECK_GL_ERROR() gpu::gl::checkGLErrorDebug(__FUNCTION__) + +#endif + + + diff --git a/libraries/gpu-gl/src/gpu/gl/GLState.cpp b/libraries/gpu-gl/src/gpu/gl/GLState.cpp new file mode 100644 index 0000000000..8cb2efa7b4 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLState.cpp @@ -0,0 +1,233 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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 "GLState.h" +#include "GLBackend.h" + +using namespace gpu; +using namespace gpu::gl; + +typedef GLState::Command Command; +typedef GLState::CommandPointer CommandPointer; +typedef GLState::Command1 Command1U; +typedef GLState::Command1 Command1I; +typedef GLState::Command1 Command1B; +typedef GLState::Command1 CommandDepthBias; +typedef GLState::Command1 CommandDepthTest; +typedef GLState::Command3 CommandStencil; +typedef GLState::Command1 CommandBlend; + +const GLState::Commands makeResetStateCommands(); + +// NOTE: This must stay in sync with the ordering of the State::Field enum +const GLState::Commands makeResetStateCommands() { + // Since State::DEFAULT is a static defined in another .cpp the initialisation order is random + // and we have a 50/50 chance that State::DEFAULT is not yet initialized. + // Since State::DEFAULT = State::Data() it is much easier to not use the actual State::DEFAULT + // but another State::Data object with a default initialization. + const State::Data DEFAULT = State::Data(); + + auto depthBiasCommand = std::make_shared(&GLBackend::do_setStateDepthBias, + Vec2(DEFAULT.depthBias, DEFAULT.depthBiasSlopeScale)); + auto stencilCommand = std::make_shared(&GLBackend::do_setStateStencil, DEFAULT.stencilActivation, + DEFAULT.stencilTestFront, DEFAULT.stencilTestBack); + + // The state commands to reset to default, + // WARNING depending on the order of the State::Field enum + return { + std::make_shared(&GLBackend::do_setStateFillMode, DEFAULT.fillMode), + std::make_shared(&GLBackend::do_setStateCullMode, DEFAULT.cullMode), + std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, DEFAULT.frontFaceClockwise), + std::make_shared(&GLBackend::do_setStateDepthClampEnable, DEFAULT.depthClampEnable), + std::make_shared(&GLBackend::do_setStateScissorEnable, DEFAULT.scissorEnable), + std::make_shared(&GLBackend::do_setStateMultisampleEnable, DEFAULT.multisampleEnable), + std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, DEFAULT.antialisedLineEnable), + + // Depth bias has 2 fields in State but really one call in GLBackend + CommandPointer(depthBiasCommand), + CommandPointer(depthBiasCommand), + + std::make_shared(&GLBackend::do_setStateDepthTest, DEFAULT.depthTest), + + // Depth bias has 3 fields in State but really one call in GLBackend + CommandPointer(stencilCommand), + CommandPointer(stencilCommand), + CommandPointer(stencilCommand), + + std::make_shared(&GLBackend::do_setStateSampleMask, DEFAULT.sampleMask), + + std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable), + + std::make_shared(&GLBackend::do_setStateBlend, DEFAULT.blendFunction), + + std::make_shared(&GLBackend::do_setStateColorWriteMask, DEFAULT.colorWriteMask) + }; +} + +const GLState::Commands GLState::_resetStateCommands = makeResetStateCommands(); + + +void generateFillMode(GLState::Commands& commands, State::FillMode fillMode) { + commands.push_back(std::make_shared(&GLBackend::do_setStateFillMode, int32(fillMode))); +} + +void generateCullMode(GLState::Commands& commands, State::CullMode cullMode) { + commands.push_back(std::make_shared(&GLBackend::do_setStateCullMode, int32(cullMode))); +} + +void generateFrontFaceClockwise(GLState::Commands& commands, bool isClockwise) { + commands.push_back(std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, isClockwise)); +} + +void generateDepthClampEnable(GLState::Commands& commands, bool enable) { + commands.push_back(std::make_shared(&GLBackend::do_setStateDepthClampEnable, enable)); +} + +void generateScissorEnable(GLState::Commands& commands, bool enable) { + commands.push_back(std::make_shared(&GLBackend::do_setStateScissorEnable, enable)); +} + +void generateMultisampleEnable(GLState::Commands& commands, bool enable) { + commands.push_back(std::make_shared(&GLBackend::do_setStateMultisampleEnable, enable)); +} + +void generateAntialiasedLineEnable(GLState::Commands& commands, bool enable) { + commands.push_back(std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, enable)); +} + +void generateDepthBias(GLState::Commands& commands, const State& state) { + commands.push_back(std::make_shared(&GLBackend::do_setStateDepthBias, Vec2(state.getDepthBias(), state.getDepthBiasSlopeScale()))); +} + +void generateDepthTest(GLState::Commands& commands, const State::DepthTest& test) { + commands.push_back(std::make_shared(&GLBackend::do_setStateDepthTest, int32(test.getRaw()))); +} + +void generateStencil(GLState::Commands& commands, const State& state) { + commands.push_back(std::make_shared(&GLBackend::do_setStateStencil, state.getStencilActivation(), state.getStencilTestFront(), state.getStencilTestBack())); +} + +void generateAlphaToCoverageEnable(GLState::Commands& commands, bool enable) { + commands.push_back(std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, enable)); +} + +void generateSampleMask(GLState::Commands& commands, uint32 mask) { + commands.push_back(std::make_shared(&GLBackend::do_setStateSampleMask, mask)); +} + +void generateBlend(GLState::Commands& commands, const State& state) { + commands.push_back(std::make_shared(&GLBackend::do_setStateBlend, state.getBlendFunction())); +} + +void generateColorWriteMask(GLState::Commands& commands, uint32 mask) { + commands.push_back(std::make_shared(&GLBackend::do_setStateColorWriteMask, mask)); +} + +GLState* GLState::sync(const State& state) { + GLState* object = Backend::getGPUObject(state); + + // If GPU object already created then good + if (object) { + return object; + } + + // Else allocate and create the GLState + if (!object) { + object = new GLState(); + Backend::setGPUObject(state, object); + } + + // here, we need to regenerate something so let's do it all + object->_commands.clear(); + object->_stamp = state.getStamp(); + object->_signature = state.getSignature(); + + bool depthBias = false; + bool stencilState = false; + + // go thorugh the list of state fields in the State and record the corresponding gl command + for (int i = 0; i < State::NUM_FIELDS; i++) { + if (state.getSignature()[i]) { + switch (i) { + case State::FILL_MODE: { + generateFillMode(object->_commands, state.getFillMode()); + break; + } + case State::CULL_MODE: { + generateCullMode(object->_commands, state.getCullMode()); + break; + } + case State::DEPTH_BIAS: + case State::DEPTH_BIAS_SLOPE_SCALE: { + depthBias = true; + break; + } + case State::FRONT_FACE_CLOCKWISE: { + generateFrontFaceClockwise(object->_commands, state.isFrontFaceClockwise()); + break; + } + case State::DEPTH_CLAMP_ENABLE: { + generateDepthClampEnable(object->_commands, state.isDepthClampEnable()); + break; + } + case State::SCISSOR_ENABLE: { + generateScissorEnable(object->_commands, state.isScissorEnable()); + break; + } + case State::MULTISAMPLE_ENABLE: { + generateMultisampleEnable(object->_commands, state.isMultisampleEnable()); + break; + } + case State::ANTIALISED_LINE_ENABLE: { + generateAntialiasedLineEnable(object->_commands, state.isAntialiasedLineEnable()); + break; + } + case State::DEPTH_TEST: { + generateDepthTest(object->_commands, state.getDepthTest()); + break; + } + + case State::STENCIL_ACTIVATION: + case State::STENCIL_TEST_FRONT: + case State::STENCIL_TEST_BACK: { + stencilState = true; + break; + } + + case State::SAMPLE_MASK: { + generateSampleMask(object->_commands, state.getSampleMask()); + break; + } + case State::ALPHA_TO_COVERAGE_ENABLE: { + generateAlphaToCoverageEnable(object->_commands, state.isAlphaToCoverageEnabled()); + break; + } + + case State::BLEND_FUNCTION: { + generateBlend(object->_commands, state); + break; + } + + case State::COLOR_WRITE_MASK: { + generateColorWriteMask(object->_commands, state.getColorWriteMask()); + break; + } + } + } + } + + if (depthBias) { + generateDepthBias(object->_commands, state); + } + + if (stencilState) { + generateStencil(object->_commands, state); + } + + return object; +} + diff --git a/libraries/gpu-gl/src/gpu/gl/GLState.h b/libraries/gpu-gl/src/gpu/gl/GLState.h new file mode 100644 index 0000000000..82635db893 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLState.h @@ -0,0 +1,73 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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_gpu_gl_GLState_h +#define hifi_gpu_gl_GLState_h + +#include "GLShared.h" + +#include + +namespace gpu { namespace gl { + +class GLBackend; +class GLState : public GPUObject { +public: + static GLState* sync(const State& state); + + class Command { + public: + virtual void run(GLBackend* backend) = 0; + Command() {} + virtual ~Command() {}; + }; + + template class Command1 : public Command { + public: + typedef void (GLBackend::*GLFunction)(T); + void run(GLBackend* backend) { (backend->*(_func))(_param); } + Command1(GLFunction func, T param) : _func(func), _param(param) {}; + GLFunction _func; + T _param; + }; + template class Command2 : public Command { + public: + typedef void (GLBackend::*GLFunction)(T, U); + void run(GLBackend* backend) { (backend->*(_func))(_param0, _param1); } + Command2(GLFunction func, T param0, U param1) : _func(func), _param0(param0), _param1(param1) {}; + GLFunction _func; + T _param0; + U _param1; + }; + + template class Command3 : public Command { + public: + typedef void (GLBackend::*GLFunction)(T, U, V); + void run(GLBackend* backend) { (backend->*(_func))(_param0, _param1, _param2); } + Command3(GLFunction func, T param0, U param1, V param2) : _func(func), _param0(param0), _param1(param1), _param2(param2) {}; + GLFunction _func; + T _param0; + U _param1; + V _param2; + }; + + typedef std::shared_ptr< Command > CommandPointer; + typedef std::vector< CommandPointer > Commands; + + Commands _commands; + Stamp _stamp; + State::Signature _signature; + + // The state commands to reset to default, + static const Commands _resetStateCommands; + + friend class GLBackend; +}; + +} } + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp similarity index 82% rename from libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp rename to libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index deb48be1ec..4bff5c87bd 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -1,59 +1,15 @@ // -// Created by Bradley Austin Davis on 2016/05/14 +// Created by Bradley Austin Davis on 2016/05/15 // Copyright 2013-2016 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 "GLBackendShared.h" -#include +#include "GLTexelFormat.h" -Q_DECLARE_LOGGING_CATEGORY(gpugllogging) -Q_LOGGING_CATEGORY(gpugllogging, "hifi.gpu.gl") - -namespace gpu { namespace gl { - -bool checkGLError(const char* name) { - GLenum error = glGetError(); - if (!error) { - return false; - } else { - switch (error) { - case GL_INVALID_ENUM: - qCDebug(gpugllogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag."; - break; - case GL_INVALID_VALUE: - qCDebug(gpugllogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag"; - break; - case GL_INVALID_OPERATION: - qCDebug(gpugllogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag.."; - break; - case GL_INVALID_FRAMEBUFFER_OPERATION: - qCDebug(gpugllogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag."; - break; - case GL_OUT_OF_MEMORY: - qCDebug(gpugllogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded."; - break; - case GL_STACK_UNDERFLOW: - qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to underflow."; - break; - case GL_STACK_OVERFLOW: - qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to overflow."; - break; - } - return true; - } -} - -bool checkGLErrorDebug(const char* name) { -#ifdef DEBUG - return checkGLError(name); -#else - Q_UNUSED(name); - return false; -#endif -} +using namespace gpu; +using namespace gpu::gl; GLTexelFormat GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { @@ -68,7 +24,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getDimension()) { case gpu::SCALAR: { texel.format = GL_RED; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -96,7 +52,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC2: { texel.format = GL_RG; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -113,7 +69,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC3: { texel.format = GL_RGB; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -135,7 +91,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC4: { texel.format = GL_RGBA; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (srcFormat.getSemantic()) { case gpu::BGRA: @@ -205,7 +161,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getDimension()) { case gpu::SCALAR: { texel.format = GL_RED; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::COMPRESSED_R: { @@ -340,7 +296,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC2: { texel.format = GL_RG; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -357,7 +313,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC3: { texel.format = GL_RGB; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -382,7 +338,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC4: { texel.format = GL_RGBA; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -468,5 +424,3 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E return texel; } } - -} } diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h new file mode 100644 index 0000000000..bc3ec55066 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h @@ -0,0 +1,32 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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_gpu_gl_GLTexelFormat_h +#define hifi_gpu_gl_GLTexelFormat_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLTexelFormat { +public: + GLenum internalFormat; + GLenum format; + GLenum type; + + static GLTexelFormat evalGLTexelFormat(const Element& dstFormat) { + return evalGLTexelFormat(dstFormat, dstFormat); + } + static GLTexelFormat evalGLTexelFormatInternal(const Element& dstFormat); + + static GLTexelFormat evalGLTexelFormat(const Element& dstFormat, const Element& srcFormat); +}; + +} } + + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp new file mode 100644 index 0000000000..74428b53f5 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -0,0 +1,296 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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 "GLTexture.h" + +#include + +#include "GLTextureTransfer.h" + +using namespace gpu; +using namespace gpu::gl; + +std::shared_ptr GLTexture::_textureTransferHelper; +static std::map _textureCountByMips; +static uint16 _currentMaxMipCount { 0 }; + +// FIXME placeholder for texture memory over-use +#define DEFAULT_MAX_MEMORY_MB 256 + +const GLenum GLTexture::CUBE_FACE_LAYOUT[6] = { + GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z +}; + +const GLenum GLTexture::WRAP_MODES[Sampler::NUM_WRAP_MODES] = { + GL_REPEAT, // WRAP_REPEAT, + GL_MIRRORED_REPEAT, // WRAP_MIRROR, + GL_CLAMP_TO_EDGE, // WRAP_CLAMP, + GL_CLAMP_TO_BORDER, // WRAP_BORDER, + GL_MIRROR_CLAMP_TO_EDGE_EXT // WRAP_MIRROR_ONCE, +}; + +const GLFilterMode GLTexture::FILTER_MODES[Sampler::NUM_FILTERS] = { + { GL_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_POINT, + { GL_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR, + { GL_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT, + { GL_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR, + + { GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_MIP_POINT, + { GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_MAG_POINT_MIP_LINEAR, + { GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, + { GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_POINT_MAG_MIP_LINEAR, + { GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_MIP_POINT, + { GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, + { GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR_MIP_POINT, + { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_MIP_LINEAR, + { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR } //FILTER_ANISOTROPIC, +}; + +GLenum GLTexture::getGLTextureType(const Texture& texture) { + switch (texture.getType()) { + case Texture::TEX_2D: + return GL_TEXTURE_2D; + break; + + case Texture::TEX_CUBE: + return GL_TEXTURE_CUBE_MAP; + break; + + default: + qFatal("Unsupported texture type"); + } + Q_UNREACHABLE(); + return GL_TEXTURE_2D; +} + + +const std::vector& GLTexture::getFaceTargets(GLenum target) { + static std::vector cubeFaceTargets { + GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z + }; + static std::vector faceTargets { + GL_TEXTURE_2D + }; + switch (target) { + case GL_TEXTURE_2D: + return faceTargets; + case GL_TEXTURE_CUBE_MAP: + return cubeFaceTargets; + default: + Q_UNREACHABLE(); + break; + } + Q_UNREACHABLE(); + return faceTargets; +} + +float GLTexture::getMemoryPressure() { + // Check for an explicit memory limit + auto availableTextureMemory = Texture::getAllowedGPUMemoryUsage(); + + // If no memory limit has been set, use a percentage of the total dedicated memory + if (!availableTextureMemory) { + auto totalGpuMemory = gpu::gl::getDedicatedMemory(); + + // If no limit has been explicitly set, and the dedicated memory can't be determined, + // just use a fallback fixed value of 256 MB + if (!totalGpuMemory) { + totalGpuMemory = MB_TO_BYTES(DEFAULT_MAX_MEMORY_MB); + } + + // Allow 75% of all available GPU memory to be consumed by textures + // FIXME overly conservative? + availableTextureMemory = (totalGpuMemory >> 2) * 3; + } + + // Return the consumed texture memory divided by the available texture memory. + auto consumedGpuMemory = Context::getTextureGPUMemoryUsage(); + return (float)consumedGpuMemory / (float)availableTextureMemory; +} + +GLTexture::DownsampleSource::DownsampleSource(GLTexture* oldTexture) : + _texture(oldTexture ? oldTexture->takeOwnership() : 0), + _minMip(oldTexture ? oldTexture->_minMip : 0), + _maxMip(oldTexture ? oldTexture->_maxMip : 0) +{ +} + +GLTexture::DownsampleSource::~DownsampleSource() { + if (_texture) { + glDeleteTextures(1, &_texture); + Backend::decrementTextureGPUCount(); + } +} + +GLTexture::GLTexture(const gpu::Texture& texture, GLuint id, GLTexture* originalTexture, bool transferrable) : + GLObject(texture, id), + _storageStamp(texture.getStamp()), + _target(getGLTextureType(texture)), + _maxMip(texture.maxMip()), + _minMip(texture.minMip()), + _virtualSize(texture.evalTotalSize()), + _transferrable(transferrable), + _downsampleSource(originalTexture) +{ + if (_transferrable) { + uint16 mipCount = usedMipLevels(); + _currentMaxMipCount = std::max(_currentMaxMipCount, mipCount); + if (!_textureCountByMips.count(mipCount)) { + _textureCountByMips[mipCount] = 1; + } else { + ++_textureCountByMips[mipCount]; + } + } + Backend::incrementTextureGPUCount(); + Backend::updateTextureGPUVirtualMemoryUsage(0, _virtualSize); +} + + +// Create the texture and allocate storage +GLTexture::GLTexture(const Texture& texture, GLuint id, bool transferrable) : + GLTexture(texture, id, nullptr, transferrable) +{ + // FIXME, do during allocation + //Backend::updateTextureGPUMemoryUsage(0, _size); + Backend::setGPUObject(texture, this); +} + +// Create the texture and copy from the original higher resolution version +GLTexture::GLTexture(const gpu::Texture& texture, GLuint id, GLTexture* originalTexture) : + GLTexture(texture, id, originalTexture, originalTexture->_transferrable) +{ + Q_ASSERT(_minMip >= originalTexture->_minMip); + // Set the GPU object last because that implicitly destroys the originalTexture object + Backend::setGPUObject(texture, this); +} + +GLTexture::~GLTexture() { + if (_transferrable) { + uint16 mipCount = usedMipLevels(); + Q_ASSERT(_textureCountByMips.count(mipCount)); + auto& numTexturesForMipCount = _textureCountByMips[mipCount]; + --numTexturesForMipCount; + if (0 == numTexturesForMipCount) { + _textureCountByMips.erase(mipCount); + if (mipCount == _currentMaxMipCount) { + _currentMaxMipCount = (_textureCountByMips.empty() ? 0 : _textureCountByMips.rbegin()->first); + } + } + } + + if (_id) { + glDeleteTextures(1, &_id); + const_cast(_id) = 0; + Backend::decrementTextureGPUCount(); + } + Backend::updateTextureGPUMemoryUsage(_size, 0); + Backend::updateTextureGPUVirtualMemoryUsage(_virtualSize, 0); +} + +void GLTexture::createTexture() { + withPreservedTexture([&] { + allocateStorage(); + (void)CHECK_GL_ERROR(); + syncSampler(); + (void)CHECK_GL_ERROR(); + }); +} + +void GLTexture::setSize(GLuint size) const { + Backend::updateTextureGPUMemoryUsage(_size, size); + const_cast(_size) = size; +} + +bool GLTexture::isInvalid() const { + return _storageStamp < _gpuObject.getStamp(); +} + +bool GLTexture::isOutdated() const { + return GLSyncState::Idle == _syncState && _contentStamp < _gpuObject.getDataStamp(); +} + +bool GLTexture::isOverMaxMemory() const { + // FIXME switch to using the max mip count used from the previous frame + if (usedMipLevels() < _currentMaxMipCount) { + return false; + } + Q_ASSERT(usedMipLevels() == _currentMaxMipCount); + + if (getMemoryPressure() < 1.0f) { + return false; + } + + return true; +} + +bool GLTexture::isReady() const { + // If we have an invalid texture, we're never ready + if (isInvalid()) { + return false; + } + + // If we're out of date, but the transfer is in progress, report ready + // as a special case + auto syncState = _syncState.load(); + + if (isOutdated()) { + return Idle != syncState; + } + + if (Idle != syncState) { + return false; + } + + return true; +} + + +// Do any post-transfer operations that might be required on the main context / rendering thread +void GLTexture::postTransfer() { + setSyncState(GLSyncState::Idle); + ++_transferCount; + + //// The public gltexture becaomes available + //_id = _privateTexture; + + _downsampleSource.reset(); + + // At this point the mip pixels have been loaded, we can notify the gpu texture to abandon it's memory + switch (_gpuObject.getType()) { + case Texture::TEX_2D: + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuObject.isStoredMipFaceAvailable(i)) { + _gpuObject.notifyMipFaceGPULoaded(i); + } + } + break; + + case Texture::TEX_CUBE: + // transfer pixels from each faces + for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuObject.isStoredMipFaceAvailable(i, f)) { + _gpuObject.notifyMipFaceGPULoaded(i, f); + } + } + } + break; + + default: + qCWarning(gpugllogging) << __FUNCTION__ << " case for Texture Type " << _gpuObject.getType() << " not supported"; + break; + } +} + +void GLTexture::initTextureTransferHelper() { + _textureTransferHelper = std::make_shared(); +} diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h new file mode 100644 index 0000000000..df2d38e2f3 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h @@ -0,0 +1,199 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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_gpu_gl_GLTexture_h +#define hifi_gpu_gl_GLTexture_h + +#include "GLShared.h" +#include "GLTextureTransfer.h" + +namespace gpu { namespace gl { + +struct GLFilterMode { + GLint minFilter; + GLint magFilter; +}; + +class GLTexture : public GLObject { +public: + static void initTextureTransferHelper(); + static std::shared_ptr _textureTransferHelper; + + template + static GLTextureType* sync(const TexturePointer& texturePointer, bool needTransfer) { + const Texture& texture = *texturePointer; + if (!texture.isDefined()) { + // NO texture definition yet so let's avoid thinking + return nullptr; + } + + // If the object hasn't been created, or the object definition is out of date, drop and re-create + GLTextureType* object = Backend::getGPUObject(texture); + + // Create the texture if need be (force re-creation if the storage stamp changes + // for easier use of immutable storage) + if (!object || object->isInvalid()) { + // This automatically any previous texture + object = new GLTextureType(texture, needTransfer); + if (!object->_transferrable) { + object->createTexture(); + object->_contentStamp = texture.getDataStamp(); + object->postTransfer(); + } + } + + // Object maybe doens't neet to be tranasferred after creation + if (!object->_transferrable) { + return object; + } + + // If we just did a transfer, return the object after doing post-transfer work + if (GLSyncState::Transferred == object->getSyncState()) { + object->postTransfer(); + return object; + } + + if (object->isReady()) { + // Do we need to reduce texture memory usage? + if (object->isOverMaxMemory() && texturePointer->incremementMinMip()) { + // WARNING, this code path will essentially `delete this`, + // so no dereferencing of this instance should be done past this point + object = new GLTextureType(texture, object); + _textureTransferHelper->transferTexture(texturePointer); + } + } else if (object->isOutdated()) { + // Object might be outdated, if so, start the transfer + // (outdated objects that are already in transfer will have reported 'true' for ready() + _textureTransferHelper->transferTexture(texturePointer); + } + + return object; + } + + template + static GLuint getId(const TexturePointer& texture, bool shouldSync) { + if (!texture) { + return 0; + } + GLTextureType* object { nullptr }; + if (shouldSync) { + object = sync(texture, shouldSync); + } else { + object = Backend::getGPUObject(*texture); + } + if (!object) { + return 0; + } + + GLuint result = object->_id; + + // Don't return textures that are in transfer state + if ((object->getSyncState() != GLSyncState::Idle) || + // Don't return transferrable textures that have never completed transfer + (!object->_transferrable || 0 != object->_transferCount)) { + // Will be either 0 or the original texture being downsampled. + result = object->_downsampleSource._texture; + } + + return result; + } + + // Used by derived classes and helpers to ensure the actual GL object exceeds the lifetime of `this` + GLuint takeOwnership() { + GLuint result = _id; + const_cast(_id) = 0; + return result; + } + + ~GLTexture(); + + const GLuint& _texture { _id }; + const Stamp _storageStamp; + const GLenum _target; + const uint16 _maxMip; + const uint16 _minMip; + const GLuint _virtualSize; // theoretical size as expected + Stamp _contentStamp { 0 }; + const bool _transferrable; + Size _transferCount { 0 }; + + struct DownsampleSource { + using Pointer = std::shared_ptr; + DownsampleSource() : _texture(0), _minMip(0), _maxMip(0) {} + DownsampleSource(GLTexture* originalTexture); + ~DownsampleSource(); + void reset() const { const_cast(_texture) = 0; } + const GLuint _texture { 0 }; + const uint16 _minMip { 0 }; + const uint16 _maxMip { 0 }; + } _downsampleSource; + + GLuint size() const { return _size; } + GLSyncState getSyncState() const { return _syncState; } + + // Is the storage out of date relative to the gpu texture? + bool isInvalid() const; + + // Is the content out of date relative to the gpu texture? + bool isOutdated() const; + + // Is the texture in a state where it can be rendered with no work? + bool isReady() const; + + // Execute any post-move operations that must occur only on the main thread + void postTransfer(); + + bool isOverMaxMemory() const; + +protected: + static const size_t CUBE_NUM_FACES = 6; + static const GLenum CUBE_FACE_LAYOUT[6]; + static const GLFilterMode FILTER_MODES[Sampler::NUM_FILTERS]; + static const GLenum WRAP_MODES[Sampler::NUM_WRAP_MODES]; + + static const std::vector& getFaceTargets(GLenum textureType); + + static GLenum getGLTextureType(const Texture& texture); + // Return a floating point value indicating how much of the allowed + // texture memory we are currently consuming. A value of 0 indicates + // no texture memory usage, while a value of 1 indicates all available / allowed memory + // is consumed. A value above 1 indicates that there is a problem. + static float getMemoryPressure(); + + + const GLuint _size { 0 }; // true size as reported by the gl api + std::atomic _syncState { GLSyncState::Idle }; + + GLTexture(const Texture& texture, GLuint id, bool transferrable); + GLTexture(const Texture& texture, GLuint id, GLTexture* originalTexture); + + void setSyncState(GLSyncState syncState) { _syncState = syncState; } + uint16 usedMipLevels() const { return (_maxMip - _minMip) + 1; } + + void createTexture(); + + virtual void allocateStorage() const = 0; + virtual void updateSize() const = 0; + virtual void transfer() const = 0; + virtual void syncSampler() const = 0; + virtual void generateMips() const = 0; + virtual void withPreservedTexture(std::function f) const = 0; + +protected: + void setSize(GLuint size) const; + +private: + + GLTexture(const gpu::Texture& gpuTexture, GLuint id, GLTexture* originalTexture, bool transferrable); + + friend class GLTextureTransferHelper; + friend class GLBackend; +}; + +} } + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.cpp b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp similarity index 84% rename from libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.cpp rename to libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp index a9635e8307..7acb736063 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp @@ -5,21 +5,19 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GLBackendTextureTransfer.h" +#include "GLTextureTransfer.h" #ifdef THREADED_TEXTURE_TRANSFER #include #include #endif - -#include "GLBackendShared.h" +#include "GLShared.h" +#include "GLTexture.h" using namespace gpu; using namespace gpu::gl; -#include "GLBackend.h" - GLTextureTransferHelper::GLTextureTransferHelper() { #ifdef THREADED_TEXTURE_TRANSFER _canvas = QSharedPointer(new OffscreenGLCanvas(), &QObject::deleteLater); @@ -45,7 +43,7 @@ GLTextureTransferHelper::~GLTextureTransferHelper() { } void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texturePointer) { - GLBackend::GLTexture* object = Backend::getGPUObject(*texturePointer); + GLTexture* object = Backend::getGPUObject(*texturePointer); Backend::incrementTextureGPUTransferCount(); #ifdef THREADED_TEXTURE_TRANSFER GLsync fence { 0 }; @@ -53,14 +51,14 @@ void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texture //glFlush(); TextureTransferPackage package { texturePointer, fence }; - object->setSyncState(GLBackend::GLTexture::Pending); + object->setSyncState(GLSyncState::Pending); queueItem(package); #else object->withPreservedTexture([&] { do_transfer(*object); }); object->_contentStamp = texturePointer->getDataStamp(); - object->setSyncState(GLBackend::GLTexture::Transferred); + object->setSyncState(GLSyncState::Transferred); #endif } @@ -78,7 +76,7 @@ void GLTextureTransferHelper::shutdown() { #endif } -void GLTextureTransferHelper::do_transfer(GLBackend::GLTexture& texture) { +void GLTextureTransferHelper::do_transfer(GLTexture& texture) { texture.createTexture(); texture.transfer(); texture.updateSize(); @@ -99,7 +97,7 @@ bool GLTextureTransferHelper::processQueueItems(const Queue& messages) { package.fence = 0; } - GLBackend::GLTexture* object = Backend::getGPUObject(*texturePointer); + GLTexture* object = Backend::getGPUObject(*texturePointer); do_transfer(*object); glBindTexture(object->_target, 0); @@ -108,7 +106,7 @@ bool GLTextureTransferHelper::processQueueItems(const Queue& messages) { glDeleteSync(writeSync); object->_contentStamp = texturePointer->getDataStamp(); - object->setSyncState(GLBackend::GLTexture::Transferred); + object->setSyncState(GLSyncState::Transferred); } return true; } diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.h b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h similarity index 79% rename from libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.h rename to libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h index f344827e53..078ab40ee3 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h @@ -5,12 +5,15 @@ // 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_gpu_gl_GLTextureTransfer_h +#define hifi_gpu_gl_GLTextureTransfer_h #include -#include +#include + #include -#include "GLBackendShared.h" -#include "GLBackend.h" + +#include "GLShared.h" #ifdef Q_OS_WIN #define THREADED_TEXTURE_TRANSFER @@ -27,6 +30,7 @@ struct TextureTransferPackage { class GLTextureTransferHelper : public GenericQueueThread { public: + using Pointer = std::shared_ptr; GLTextureTransferHelper(); ~GLTextureTransferHelper(); void transferTexture(const gpu::TexturePointer& texturePointer); @@ -36,10 +40,12 @@ protected: void setup() override; void shutdown() override; bool processQueueItems(const Queue& messages) override; - void do_transfer(GLBackend::GLTexture& texturePointer); + void do_transfer(GLTexture& texturePointer); private: QSharedPointer _canvas; }; } } + +#endif \ No newline at end of file diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp new file mode 100644 index 0000000000..93d87ee6e4 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp @@ -0,0 +1,175 @@ +// +// Created by Sam Gateau on 10/27/2014. +// 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 "GL41Backend.h" + +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(gpugl41logging, "hifi.gpu.gl41") + +using namespace gpu; +using namespace gpu::gl41; + +void GL41Backend::do_draw(Batch& batch, size_t paramOffset) { + Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; + uint32 numVertices = batch._params[paramOffset + 1]._uint; + uint32 startVertex = batch._params[paramOffset + 0]._uint; + + if (isStereo()) { + setupStereoSide(0); + glDrawArrays(mode, startVertex, numVertices); + setupStereoSide(1); + glDrawArrays(mode, startVertex, numVertices); + + _stats._DSNumTriangles += 2 * numVertices / 3; + _stats._DSNumDrawcalls += 2; + + } else { + glDrawArrays(mode, startVertex, numVertices); + _stats._DSNumTriangles += numVertices / 3; + _stats._DSNumDrawcalls++; + } + _stats._DSNumAPIDrawcalls++; + + (void) CHECK_GL_ERROR(); +} + +void GL41Backend::do_drawIndexed(Batch& batch, size_t paramOffset) { + Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; + uint32 numIndices = batch._params[paramOffset + 1]._uint; + uint32 startIndex = batch._params[paramOffset + 0]._uint; + + GLenum glType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType]; + + auto typeByteSize = TYPE_SIZE[_input._indexBufferType]; + GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); + + if (isStereo()) { + setupStereoSide(0); + glDrawElements(mode, numIndices, glType, indexBufferByteOffset); + setupStereoSide(1); + glDrawElements(mode, numIndices, glType, indexBufferByteOffset); + + _stats._DSNumTriangles += 2 * numIndices / 3; + _stats._DSNumDrawcalls += 2; + } else { + glDrawElements(mode, numIndices, glType, indexBufferByteOffset); + _stats._DSNumTriangles += numIndices / 3; + _stats._DSNumDrawcalls++; + } + _stats._DSNumAPIDrawcalls++; + + (void) CHECK_GL_ERROR(); +} + +void GL41Backend::do_drawInstanced(Batch& batch, size_t paramOffset) { + GLint numInstances = batch._params[paramOffset + 4]._uint; + Primitive primitiveType = (Primitive)batch._params[paramOffset + 3]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; + uint32 numVertices = batch._params[paramOffset + 2]._uint; + uint32 startVertex = batch._params[paramOffset + 1]._uint; + + + if (isStereo()) { + GLint trueNumInstances = 2 * numInstances; + + setupStereoSide(0); + glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); + setupStereoSide(1); + glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); + + _stats._DSNumTriangles += (trueNumInstances * numVertices) / 3; + _stats._DSNumDrawcalls += trueNumInstances; + } else { + glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); + _stats._DSNumTriangles += (numInstances * numVertices) / 3; + _stats._DSNumDrawcalls += numInstances; + } + _stats._DSNumAPIDrawcalls++; + + (void) CHECK_GL_ERROR(); +} + +void glbackend_glDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex, GLuint baseinstance) { +#if (GPU_INPUT_PROFILE == GPU_CORE_43) + glDrawElementsInstancedBaseVertexBaseInstance(mode, count, type, indices, primcount, basevertex, baseinstance); +#else + glDrawElementsInstanced(mode, count, type, indices, primcount); +#endif +} + +void GL41Backend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { + GLint numInstances = batch._params[paramOffset + 4]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 3]._uint]; + uint32 numIndices = batch._params[paramOffset + 2]._uint; + uint32 startIndex = batch._params[paramOffset + 1]._uint; + // FIXME glDrawElementsInstancedBaseVertexBaseInstance is only available in GL 4.3 + // and higher, so currently we ignore this field + uint32 startInstance = batch._params[paramOffset + 0]._uint; + GLenum glType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType]; + + auto typeByteSize = TYPE_SIZE[_input._indexBufferType]; + GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); + + if (isStereo()) { + GLint trueNumInstances = 2 * numInstances; + + setupStereoSide(0); + glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); + setupStereoSide(1); + glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); + + _stats._DSNumTriangles += (trueNumInstances * numIndices) / 3; + _stats._DSNumDrawcalls += trueNumInstances; + } else { + glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); + _stats._DSNumTriangles += (numInstances * numIndices) / 3; + _stats._DSNumDrawcalls += numInstances; + } + + _stats._DSNumAPIDrawcalls++; + + (void)CHECK_GL_ERROR(); +} + + +void GL41Backend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { +#if (GPU_INPUT_PROFILE == GPU_CORE_43) + uint commandCount = batch._params[paramOffset + 0]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; + + glMultiDrawArraysIndirect(mode, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); + _stats._DSNumDrawcalls += commandCount; + _stats._DSNumAPIDrawcalls++; + +#else + // FIXME implement the slow path +#endif + (void)CHECK_GL_ERROR(); + +} + +void GL41Backend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) { +#if (GPU_INPUT_PROFILE == GPU_CORE_43) + uint commandCount = batch._params[paramOffset + 0]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; + GLenum indexType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType]; + + glMultiDrawElementsIndirect(mode, indexType, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); + _stats._DSNumDrawcalls += commandCount; + _stats._DSNumAPIDrawcalls++; +#else + // FIXME implement the slow path +#endif + (void)CHECK_GL_ERROR(); +} diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h new file mode 100644 index 0000000000..5695ba080e --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -0,0 +1,96 @@ +// +// GL41Backend.h +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 10/27/2014. +// 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_gpu_41_GL41Backend_h +#define hifi_gpu_41_GL41Backend_h + +#include + +#include "../gl/GLBackend.h" +#include "../gl/GLTexture.h" + +#define GPU_CORE_41 410 +#define GPU_CORE_43 430 + +#ifdef Q_OS_MAC +#define GPU_INPUT_PROFILE GPU_CORE_41 +#else +#define GPU_INPUT_PROFILE GPU_CORE_43 +#endif + +namespace gpu { namespace gl41 { + +class GL41Backend : public gl::GLBackend { + using Parent = gl::GLBackend; + // Context Backend static interface required + friend class Context; + +public: + explicit GL41Backend(bool syncCache) : Parent(syncCache) {} + GL41Backend() : Parent() {} + + class GL41Texture : public gpu::gl::GLTexture { + using Parent = gpu::gl::GLTexture; + GLuint allocate(); + public: + GL41Texture(const Texture& buffer, bool transferrable); + GL41Texture(const Texture& buffer, GL41Texture* original); + + protected: + void transferMip(uint16_t mipLevel, uint8_t face = 0) const; + void allocateStorage() const override; + void updateSize() const override; + void transfer() const override; + void syncSampler() const override; + void generateMips() const override; + void withPreservedTexture(std::function f) const override; + }; + + +protected: + GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; + gl::GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; + + GLuint getBufferID(const Buffer& buffer) override; + gl::GLBuffer* syncGPUObject(const Buffer& buffer) override; + + GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) override; + gl::GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) override; + + GLuint getQueryID(const QueryPointer& query) override; + gl::GLQuery* syncGPUObject(const Query& query) override; + + // Draw Stage + void do_draw(Batch& batch, size_t paramOffset) override; + void do_drawIndexed(Batch& batch, size_t paramOffset) override; + void do_drawInstanced(Batch& batch, size_t paramOffset) override; + void do_drawIndexedInstanced(Batch& batch, size_t paramOffset) override; + void do_multiDrawIndirect(Batch& batch, size_t paramOffset) override; + void do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) override; + + // Input Stage + void updateInput() override; + + // Synchronize the state cache of this Backend with the actual real state of the GL Context + void transferTransformState(const Batch& batch) const override; + void initTransform() override; + void updateTransform(const Batch& batch); + void resetTransformStage(); + + // Output stage + void do_blit(Batch& batch, size_t paramOffset) override; +}; + +} } + +Q_DECLARE_LOGGING_CATEGORY(gpugl41logging) + + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp new file mode 100644 index 0000000000..ac337550ca --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp @@ -0,0 +1,62 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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 "GL41Backend.h" +#include "../gl/GLBuffer.h" + +using namespace gpu; +using namespace gpu::gl41; + +class GL41Buffer : public gl::GLBuffer { + using Parent = gpu::gl::GLBuffer; + static GLuint allocate() { + GLuint result; + glGenBuffers(1, &result); + return result; + } + +public: + GL41Buffer(const Buffer& buffer, GL41Buffer* original) : Parent(buffer, allocate()) { + glBindBuffer(GL_ARRAY_BUFFER, _buffer); + glBufferData(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + if (original && original->_size) { + glBindBuffer(GL_COPY_WRITE_BUFFER, _buffer); + glBindBuffer(GL_COPY_READ_BUFFER, original->_buffer); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, original->_size); + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + glBindBuffer(GL_COPY_READ_BUFFER, 0); + (void)CHECK_GL_ERROR(); + } + Backend::setGPUObject(buffer, this); + } + + void transfer() override { + glBindBuffer(GL_ARRAY_BUFFER, _buffer); + (void)CHECK_GL_ERROR(); + Size offset; + Size size; + Size currentPage { 0 }; + auto data = _gpuObject.getSysmem().readData(); + while (_gpuObject.getNextTransferBlock(offset, size, currentPage)) { + glBufferSubData(GL_ARRAY_BUFFER, offset, size, data + offset); + (void)CHECK_GL_ERROR(); + } + glBindBuffer(GL_ARRAY_BUFFER, 0); + (void)CHECK_GL_ERROR(); + _gpuObject._flags &= ~Buffer::DIRTY; + } +}; + +GLuint GL41Backend::getBufferID(const Buffer& buffer) { + return GL41Buffer::getId(buffer); +} + +gl::GLBuffer* GL41Backend::syncGPUObject(const Buffer& buffer) { + return GL41Buffer::sync(buffer); +} diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp new file mode 100644 index 0000000000..59c4050af1 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp @@ -0,0 +1,108 @@ +// +// GL41BackendInput.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 3/8/2015. +// 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 "GL41Backend.h" + +using namespace gpu; +using namespace gpu::gl41; + +void GL41Backend::updateInput() { + if (_input._invalidFormat || _input._invalidBuffers.any()) { + + if (_input._invalidFormat) { + InputStageState::ActivationCache newActivation; + + _stats._ISNumFormatChanges++; + + // Check expected activation + if (_input._format) { + for (auto& it : _input._format->getAttributes()) { + const Stream::Attribute& attrib = (it).second; + uint8_t locationCount = attrib._element.getLocationCount(); + for (int i = 0; i < locationCount; ++i) { + newActivation.set(attrib._slot + i); + } + } + } + + // Manage Activation what was and what is expected now + for (unsigned int i = 0; i < newActivation.size(); i++) { + bool newState = newActivation[i]; + if (newState != _input._attributeActivation[i]) { + + if (newState) { + glEnableVertexAttribArray(i); + } else { + glDisableVertexAttribArray(i); + } + (void)CHECK_GL_ERROR(); + + _input._attributeActivation.flip(i); + } + } + } + + // now we need to bind the buffers and assign the attrib pointers + if (_input._format) { + const Buffers& buffers = _input._buffers; + const Offsets& offsets = _input._bufferOffsets; + const Offsets& strides = _input._bufferStrides; + + const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); + auto& inputChannels = _input._format->getChannels(); + _stats._ISNumInputBufferChanges++; + + GLuint boundVBO = 0; + for (auto& channelIt : inputChannels) { + const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; + if ((channelIt).first < buffers.size()) { + int bufferNum = (channelIt).first; + + if (_input._invalidBuffers.test(bufferNum) || _input._invalidFormat) { + // GLuint vbo = gpu::GL41Backend::getBufferID((*buffers[bufferNum])); + GLuint vbo = _input._bufferVBOs[bufferNum]; + if (boundVBO != vbo) { + glBindBuffer(GL_ARRAY_BUFFER, vbo); + (void)CHECK_GL_ERROR(); + boundVBO = vbo; + } + _input._invalidBuffers[bufferNum] = false; + + for (unsigned int i = 0; i < channel._slots.size(); i++) { + const Stream::Attribute& attrib = attributes.at(channel._slots[i]); + GLuint slot = attrib._slot; + GLuint count = attrib._element.getLocationScalarCount(); + uint8_t locationCount = attrib._element.getLocationCount(); + GLenum type = gl::ELEMENT_TYPE_TO_GL[attrib._element.getType()]; + // GLenum perLocationStride = strides[bufferNum]; + GLenum perLocationStride = attrib._element.getLocationSize(); + 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 + (GLuint)locNum, count, type, isNormalized, stride, + reinterpret_cast(pointer + perLocationStride * (GLuint)locNum)); + glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency); + } + + // TODO: Support properly the IAttrib version + + (void)CHECK_GL_ERROR(); + } + } + } + } + } + // everything format related should be in sync now + _input._invalidFormat = false; + } +} + diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp new file mode 100644 index 0000000000..53c2c75394 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp @@ -0,0 +1,169 @@ +// +// GL41BackendTexture.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 1/19/2015. +// 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 "GL41Backend.h" + +#include + +#include "../gl/GLFramebuffer.h" +#include "../gl/GLTexture.h" + +namespace gpu { namespace gl41 { + +class GL41Framebuffer : public gl::GLFramebuffer { + using Parent = gl::GLFramebuffer; + static GLuint allocate() { + GLuint result; + glGenFramebuffers(1, &result); + return result; + } +public: + void update() override { + GLint currentFBO = -1; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); + glBindFramebuffer(GL_FRAMEBUFFER, _fbo); + gl::GLTexture* gltexture = nullptr; + TexturePointer surface; + if (_gpuObject.getColorStamps() != _colorStamps) { + if (_gpuObject.hasColor()) { + _colorBuffers.clear(); + static const GLenum colorAttachments[] = { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3, + GL_COLOR_ATTACHMENT4, + GL_COLOR_ATTACHMENT5, + GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT7, + GL_COLOR_ATTACHMENT8, + GL_COLOR_ATTACHMENT9, + GL_COLOR_ATTACHMENT10, + GL_COLOR_ATTACHMENT11, + GL_COLOR_ATTACHMENT12, + GL_COLOR_ATTACHMENT13, + GL_COLOR_ATTACHMENT14, + GL_COLOR_ATTACHMENT15 }; + + int unit = 0; + for (auto& b : _gpuObject.getRenderBuffers()) { + surface = b._texture; + if (surface) { + gltexture = gl::GLTexture::sync(surface, false); // Grab the gltexture and don't transfer + } else { + gltexture = nullptr; + } + + if (gltexture) { + glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); + _colorBuffers.push_back(colorAttachments[unit]); + } else { + glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, 0, 0); + } + unit++; + } + } + _colorStamps = _gpuObject.getColorStamps(); + } + + GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT; + if (!_gpuObject.hasStencil()) { + attachement = GL_DEPTH_ATTACHMENT; + } else if (!_gpuObject.hasDepth()) { + attachement = GL_STENCIL_ATTACHMENT; + } + + if (_gpuObject.getDepthStamp() != _depthStamp) { + auto surface = _gpuObject.getDepthStencilBuffer(); + if (_gpuObject.hasDepthStencil() && surface) { + gltexture = gl::GLTexture::sync(surface, false); // Grab the gltexture and don't transfer + } + + if (gltexture) { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); + } else { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, 0, 0); + } + _depthStamp = _gpuObject.getDepthStamp(); + } + + + // Last but not least, define where we draw + if (!_colorBuffers.empty()) { + glDrawBuffers((GLsizei)_colorBuffers.size(), _colorBuffers.data()); + } else { + glDrawBuffer(GL_NONE); + } + + // Now check for completness + _status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + // restore the current framebuffer + if (currentFBO != -1) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO); + } + + checkStatus(GL_DRAW_FRAMEBUFFER); + } + + +public: + GL41Framebuffer(const gpu::Framebuffer& framebuffer) + : Parent(framebuffer, allocate()) { } +}; + +gl::GLFramebuffer* GL41Backend::syncGPUObject(const Framebuffer& framebuffer) { + return GL41Framebuffer::sync(framebuffer); +} + +GLuint GL41Backend::getFramebufferID(const FramebufferPointer& framebuffer) { + return framebuffer ? GL41Framebuffer::getId(*framebuffer) : 0; +} + +void GL41Backend::do_blit(Batch& batch, size_t paramOffset) { + auto srcframebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); + Vec4i srcvp; + for (auto i = 0; i < 4; ++i) { + srcvp[i] = batch._params[paramOffset + 1 + i]._int; + } + + auto dstframebuffer = batch._framebuffers.get(batch._params[paramOffset + 5]._uint); + Vec4i dstvp; + for (auto i = 0; i < 4; ++i) { + dstvp[i] = batch._params[paramOffset + 6 + i]._int; + } + + // Assign dest framebuffer if not bound already + auto newDrawFBO = getFramebufferID(dstframebuffer); + if (_output._drawFBO != newDrawFBO) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, newDrawFBO); + } + + // always bind the read fbo + glBindFramebuffer(GL_READ_FRAMEBUFFER, getFramebufferID(srcframebuffer)); + + // Blit! + glBlitFramebuffer(srcvp.x, srcvp.y, srcvp.z, srcvp.w, + dstvp.x, dstvp.y, dstvp.z, dstvp.w, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + + // Always clean the read fbo to 0 + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + // Restore draw fbo if changed + if (_output._drawFBO != newDrawFBO) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _output._drawFBO); + } + + (void) CHECK_GL_ERROR(); +} + + +} } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp new file mode 100644 index 0000000000..8cc47a43a4 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp @@ -0,0 +1,37 @@ +// +// GL41BackendQuery.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 7/7/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 +// +#include "GL41Backend.h" + +#include "../gl/GLQuery.h" + +using namespace gpu; +using namespace gpu::gl41; + +class GL41Query : public gpu::gl::GLQuery { + using Parent = gpu::gl::GLQuery; +public: + static GLuint allocateQuery() { + GLuint result; + glGenQueries(1, &result); + return result; + } + + GL41Query(const Query& query) + : Parent(query, allocateQuery(), allocateQuery()) { } +}; + +gl::GLQuery* GL41Backend::syncGPUObject(const Query& query) { + return GL41Query::sync(query); +} + +GLuint GL41Backend::getQueryID(const QueryPointer& query) { + return GL41Query::getId(query); +} diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp new file mode 100644 index 0000000000..326a63c01a --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -0,0 +1,234 @@ +// +// GL41BackendTexture.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 1/19/2015. +// 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 "GL41Backend.h" + +#include +#include +#include + +#include "../gl/GLTexelFormat.h" + +using namespace gpu; +using namespace gpu::gl41; + +using GL41TexelFormat = gl::GLTexelFormat; +using GL41Texture = GL41Backend::GL41Texture; + +GLuint GL41Texture::allocate() { + Backend::incrementTextureGPUCount(); + GLuint result; + glGenTextures(1, &result); + return result; +} + +GLuint GL41Backend::getTextureID(const TexturePointer& texture, bool transfer) { + return GL41Texture::getId(texture, transfer); +} + +gl::GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texture, bool transfer) { + return GL41Texture::sync(texture, transfer); +} + +GL41Texture::GL41Texture(const Texture& texture, bool transferrable) : gl::GLTexture(texture, allocate(), transferrable) {} + +GL41Texture::GL41Texture(const Texture& texture, GL41Texture* original) : gl::GLTexture(texture, allocate(), original) {} + +void GL41Backend::GL41Texture::withPreservedTexture(std::function f) const { + GLint boundTex = -1; + switch (_target) { + case GL_TEXTURE_2D: + glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); + break; + + case GL_TEXTURE_CUBE_MAP: + glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex); + break; + + default: + qFatal("Unsupported texture type"); + } + (void)CHECK_GL_ERROR(); + + glBindTexture(_target, _texture); + f(); + glBindTexture(_target, boundTex); + (void)CHECK_GL_ERROR(); +} + +void GL41Backend::GL41Texture::generateMips() const { + withPreservedTexture([&] { + glGenerateMipmap(_target); + }); + (void)CHECK_GL_ERROR(); +} + +void GL41Backend::GL41Texture::allocateStorage() const { + gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); + glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); + (void)CHECK_GL_ERROR(); + glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); + (void)CHECK_GL_ERROR(); + if (GLEW_VERSION_4_2 && !_gpuObject.getTexelFormat().isCompressed()) { + // Get the dimensions, accounting for the downgrade level + Vec3u dimensions = _gpuObject.evalMipDimensions(_minMip); + glTexStorage2D(_target, usedMipLevels(), texelFormat.internalFormat, dimensions.x, dimensions.y); + (void)CHECK_GL_ERROR(); + } else { + for (uint16_t l = _minMip; l <= _maxMip; l++) { + // Get the mip level dimensions, accounting for the downgrade level + Vec3u dimensions = _gpuObject.evalMipDimensions(l); + for (GLenum target : getFaceTargets(_target)) { + glTexImage2D(target, l - _minMip, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, NULL); + (void)CHECK_GL_ERROR(); + } + } + } +} + +void GL41Backend::GL41Texture::updateSize() const { + setSize(_virtualSize); + if (!_id) { + return; + } + + if (_gpuObject.getTexelFormat().isCompressed()) { + GLenum proxyType = GL_TEXTURE_2D; + GLuint numFaces = 1; + if (_gpuObject.getType() == gpu::Texture::TEX_CUBE) { + proxyType = CUBE_FACE_LAYOUT[0]; + numFaces = (GLuint)CUBE_NUM_FACES; + } + GLint gpuSize{ 0 }; + glGetTexLevelParameteriv(proxyType, 0, GL_TEXTURE_COMPRESSED, &gpuSize); + (void)CHECK_GL_ERROR(); + + if (gpuSize) { + for (GLuint level = _minMip; level < _maxMip; level++) { + GLint levelSize{ 0 }; + glGetTexLevelParameteriv(proxyType, level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &levelSize); + levelSize *= numFaces; + + if (levelSize <= 0) { + break; + } + gpuSize += levelSize; + } + (void)CHECK_GL_ERROR(); + setSize(gpuSize); + return; + } + } +} + +// Move content bits from the CPU to the GPU for a given mip / face +void GL41Backend::GL41Texture::transferMip(uint16_t mipLevel, uint8_t face) const { + auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); + gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); + //GLenum target = getFaceTargets()[face]; + GLenum target = _target == GL_TEXTURE_2D ? GL_TEXTURE_2D : CUBE_FACE_LAYOUT[face]; + auto size = _gpuObject.evalMipDimensions(mipLevel); + glTexSubImage2D(target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); + (void)CHECK_GL_ERROR(); +} + +// This should never happen on the main thread +// Move content bits from the CPU to the GPU +void GL41Backend::GL41Texture::transfer() const { + PROFILE_RANGE(__FUNCTION__); + //qDebug() << "Transferring texture: " << _privateTexture; + // Need to update the content of the GPU object from the source sysmem of the texture + if (_contentStamp >= _gpuObject.getDataStamp()) { + return; + } + + glBindTexture(_target, _id); + (void)CHECK_GL_ERROR(); + + if (_downsampleSource._texture) { + GLuint fbo { 0 }; + glGenFramebuffers(1, &fbo); + (void)CHECK_GL_ERROR(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + (void)CHECK_GL_ERROR(); + // Find the distance between the old min mip and the new one + uint16 mipOffset = _minMip - _downsampleSource._minMip; + for (uint16 i = _minMip; i <= _maxMip; ++i) { + uint16 targetMip = i - _minMip; + uint16 sourceMip = targetMip + mipOffset; + Vec3u dimensions = _gpuObject.evalMipDimensions(i); + for (GLenum target : getFaceTargets(_target)) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, _downsampleSource._texture, sourceMip); + (void)CHECK_GL_ERROR(); + glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, dimensions.x, dimensions.y); + (void)CHECK_GL_ERROR(); + } + } + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + } else { + // GO through the process of allocating the correct storage and/or update the content + switch (_gpuObject.getType()) { + case Texture::TEX_2D: + { + for (uint16_t i = _minMip; i <= _maxMip; ++i) { + if (_gpuObject.isStoredMipFaceAvailable(i)) { + transferMip(i); + } + } + } + break; + + case Texture::TEX_CUBE: + // transfer pixels from each faces + for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuObject.isStoredMipFaceAvailable(i, f)) { + transferMip(i, f); + } + } + } + break; + + default: + qCWarning(gpugl41logging) << __FUNCTION__ << " case for Texture Type " << _gpuObject.getType() << " not supported"; + break; + } + } + if (_gpuObject.isAutogenerateMips()) { + glGenerateMipmap(_target); + (void)CHECK_GL_ERROR(); + } +} + +void GL41Backend::GL41Texture::syncSampler() const { + const Sampler& sampler = _gpuObject.getSampler(); + const auto& fm = FILTER_MODES[sampler.getFilter()]; + glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, fm.minFilter); + glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, fm.magFilter); + + if (sampler.doComparison()) { + glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + glTexParameteri(_target, GL_TEXTURE_COMPARE_FUNC, gl::COMPARISON_TO_GL[sampler.getComparisonFunction()]); + } else { + glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + + glTexParameteri(_target, GL_TEXTURE_WRAP_S, WRAP_MODES[sampler.getWrapModeU()]); + glTexParameteri(_target, GL_TEXTURE_WRAP_T, WRAP_MODES[sampler.getWrapModeV()]); + glTexParameteri(_target, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]); + + glTexParameterfv(_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); + glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset()); + glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); + glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); + glTexParameterf(_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); +} + diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp new file mode 100644 index 0000000000..89b5db34c0 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp @@ -0,0 +1,83 @@ +// +// GL41BackendTransform.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 3/8/2015. +// 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 "GL41Backend.h" + +using namespace gpu; +using namespace gpu::gl41; + +void GL41Backend::initTransform() { + glGenBuffers(1, &_transform._objectBuffer); + glGenBuffers(1, &_transform._cameraBuffer); + glGenBuffers(1, &_transform._drawCallInfoBuffer); + glGenTextures(1, &_transform._objectBufferTexture); + size_t cameraSize = sizeof(TransformStageState::CameraBufferElement); + while (_transform._cameraUboSize < cameraSize) { + _transform._cameraUboSize += _uboAlignment; + } +} + +void GL41Backend::transferTransformState(const Batch& batch) const { + // FIXME not thread safe + static std::vector bufferData; + if (!_transform._cameras.empty()) { + bufferData.resize(_transform._cameraUboSize * _transform._cameras.size()); + for (size_t i = 0; i < _transform._cameras.size(); ++i) { + memcpy(bufferData.data() + (_transform._cameraUboSize * i), &_transform._cameras[i], sizeof(TransformStageState::CameraBufferElement)); + } + glBindBuffer(GL_UNIFORM_BUFFER, _transform._cameraBuffer); + glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + } + + if (!batch._objects.empty()) { + auto byteSize = batch._objects.size() * sizeof(Batch::TransformObject); + bufferData.resize(byteSize); + memcpy(bufferData.data(), batch._objects.data(), byteSize); + +#ifdef GPU_SSBO_DRAW_CALL_INFO + glBindBuffer(GL_SHADER_STORAGE_BUFFER, _transform._objectBuffer); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#else + glBindBuffer(GL_TEXTURE_BUFFER, _transform._objectBuffer); + glBufferData(GL_TEXTURE_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_TEXTURE_BUFFER, 0); +#endif + } + + if (!batch._namedData.empty()) { + bufferData.clear(); + for (auto& data : batch._namedData) { + auto currentSize = bufferData.size(); + auto bytesToCopy = data.second.drawCallInfos.size() * sizeof(Batch::DrawCallInfo); + bufferData.resize(currentSize + bytesToCopy); + memcpy(bufferData.data() + currentSize, data.second.drawCallInfos.data(), bytesToCopy); + _transform._drawCallInfoOffsets[data.first] = (GLvoid*)currentSize; + } + + glBindBuffer(GL_ARRAY_BUFFER, _transform._drawCallInfoBuffer); + glBufferData(GL_ARRAY_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + +#ifdef GPU_SSBO_DRAW_CALL_INFO + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, TRANSFORM_OBJECT_SLOT, _transform._objectBuffer); +#else + glActiveTexture(GL_TEXTURE0 + TRANSFORM_OBJECT_SLOT); + glBindTexture(GL_TEXTURE_BUFFER, _transform._objectBufferTexture); + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _transform._objectBuffer); +#endif + + CHECK_GL_ERROR(); + + // Make sure the current Camera offset is unknown before render Draw + _transform._currentCameraOffset = INVALID_OFFSET; +} diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp new file mode 100644 index 0000000000..bb6ae67233 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp @@ -0,0 +1,149 @@ +// +// Created by Sam Gateau on 10/27/2014. +// 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 "GL45Backend.h" + +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(gpugl45logging, "hifi.gpu.gl45") + +using namespace gpu; +using namespace gpu::gl45; + +void GL45Backend::do_draw(Batch& batch, size_t paramOffset) { + Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; + uint32 numVertices = batch._params[paramOffset + 1]._uint; + uint32 startVertex = batch._params[paramOffset + 0]._uint; + + if (isStereo()) { + setupStereoSide(0); + glDrawArrays(mode, startVertex, numVertices); + setupStereoSide(1); + glDrawArrays(mode, startVertex, numVertices); + + _stats._DSNumTriangles += 2 * numVertices / 3; + _stats._DSNumDrawcalls += 2; + + } else { + glDrawArrays(mode, startVertex, numVertices); + _stats._DSNumTriangles += numVertices / 3; + _stats._DSNumDrawcalls++; + } + _stats._DSNumAPIDrawcalls++; + + (void) CHECK_GL_ERROR(); +} + +void GL45Backend::do_drawIndexed(Batch& batch, size_t paramOffset) { + Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; + uint32 numIndices = batch._params[paramOffset + 1]._uint; + uint32 startIndex = batch._params[paramOffset + 0]._uint; + + GLenum glType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType]; + + auto typeByteSize = TYPE_SIZE[_input._indexBufferType]; + GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); + + if (isStereo()) { + setupStereoSide(0); + glDrawElements(mode, numIndices, glType, indexBufferByteOffset); + setupStereoSide(1); + glDrawElements(mode, numIndices, glType, indexBufferByteOffset); + + _stats._DSNumTriangles += 2 * numIndices / 3; + _stats._DSNumDrawcalls += 2; + } else { + glDrawElements(mode, numIndices, glType, indexBufferByteOffset); + _stats._DSNumTriangles += numIndices / 3; + _stats._DSNumDrawcalls++; + } + _stats._DSNumAPIDrawcalls++; + + (void) CHECK_GL_ERROR(); +} + +void GL45Backend::do_drawInstanced(Batch& batch, size_t paramOffset) { + GLint numInstances = batch._params[paramOffset + 4]._uint; + Primitive primitiveType = (Primitive)batch._params[paramOffset + 3]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; + uint32 numVertices = batch._params[paramOffset + 2]._uint; + uint32 startVertex = batch._params[paramOffset + 1]._uint; + + + if (isStereo()) { + GLint trueNumInstances = 2 * numInstances; + + setupStereoSide(0); + glDrawArraysInstanced(mode, startVertex, numVertices, numInstances); + setupStereoSide(1); + glDrawArraysInstanced(mode, startVertex, numVertices, numInstances); + + _stats._DSNumTriangles += (trueNumInstances * numVertices) / 3; + _stats._DSNumDrawcalls += trueNumInstances; + } else { + glDrawArraysInstanced(mode, startVertex, numVertices, numInstances); + _stats._DSNumTriangles += (numInstances * numVertices) / 3; + _stats._DSNumDrawcalls += numInstances; + } + _stats._DSNumAPIDrawcalls++; + + (void) CHECK_GL_ERROR(); +} + +void GL45Backend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { + GLint numInstances = batch._params[paramOffset + 4]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 3]._uint]; + uint32 numIndices = batch._params[paramOffset + 2]._uint; + uint32 startIndex = batch._params[paramOffset + 1]._uint; + uint32 startInstance = batch._params[paramOffset + 0]._uint; + GLenum glType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType]; + auto typeByteSize = TYPE_SIZE[_input._indexBufferType]; + GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); + + if (isStereo()) { + GLint trueNumInstances = 2 * numInstances; + setupStereoSide(0); + glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); + setupStereoSide(1); + glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); + _stats._DSNumTriangles += (trueNumInstances * numIndices) / 3; + _stats._DSNumDrawcalls += trueNumInstances; + } else { + glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); + _stats._DSNumTriangles += (numInstances * numIndices) / 3; + _stats._DSNumDrawcalls += numInstances; + } + + _stats._DSNumAPIDrawcalls++; + + (void)CHECK_GL_ERROR(); +} + +void GL45Backend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { + uint commandCount = batch._params[paramOffset + 0]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; + glMultiDrawArraysIndirect(mode, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); + _stats._DSNumDrawcalls += commandCount; + _stats._DSNumAPIDrawcalls++; + (void)CHECK_GL_ERROR(); +} + +void GL45Backend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) { + uint commandCount = batch._params[paramOffset + 0]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; + GLenum indexType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType]; + glMultiDrawElementsIndirect(mode, indexType, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); + _stats._DSNumDrawcalls += commandCount; + _stats._DSNumAPIDrawcalls++; + (void)CHECK_GL_ERROR(); +} diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h new file mode 100644 index 0000000000..d0dfbd0e41 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -0,0 +1,85 @@ +// +// GL45Backend.h +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 10/27/2014. +// 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_gpu_45_GL45Backend_h +#define hifi_gpu_45_GL45Backend_h + +#include "../gl/GLBackend.h" +#include "../gl/GLTexture.h" + +namespace gpu { namespace gl45 { + +class GL45Backend : public gl::GLBackend { + using Parent = gl::GLBackend; + // Context Backend static interface required + friend class Context; + +public: + explicit GL45Backend(bool syncCache) : Parent(syncCache) {} + GL45Backend() : Parent() {} + + class GL45Texture : public gpu::gl::GLTexture { + using Parent = gpu::gl::GLTexture; + GLuint allocate(const Texture& texture); + public: + GL45Texture(const Texture& texture, bool transferrable); + GL45Texture(const Texture& texture, GLTexture* original); + + protected: + void transferMip(uint16_t mipLevel, uint8_t face = 0) const; + void allocateStorage() const override; + void updateSize() const override; + void transfer() const override; + void syncSampler() const override; + void generateMips() const override; + void withPreservedTexture(std::function f) const override; + }; + + +protected: + GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; + gl::GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; + + GLuint getBufferID(const Buffer& buffer) override; + gl::GLBuffer* syncGPUObject(const Buffer& buffer) override; + + GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) override; + gl::GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) override; + + GLuint getQueryID(const QueryPointer& query) override; + gl::GLQuery* syncGPUObject(const Query& query) override; + + // Draw Stage + void do_draw(Batch& batch, size_t paramOffset) override; + void do_drawIndexed(Batch& batch, size_t paramOffset) override; + void do_drawInstanced(Batch& batch, size_t paramOffset) override; + void do_drawIndexedInstanced(Batch& batch, size_t paramOffset) override; + void do_multiDrawIndirect(Batch& batch, size_t paramOffset) override; + void do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) override; + + // Input Stage + void updateInput() override; + + // Synchronize the state cache of this Backend with the actual real state of the GL Context + void transferTransformState(const Batch& batch) const override; + void initTransform() override; + void updateTransform(const Batch& batch); + void resetTransformStage(); + + // Output stage + void do_blit(Batch& batch, size_t paramOffset) override; +}; + +} } + +Q_DECLARE_LOGGING_CATEGORY(gpugl45logging) + + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp new file mode 100644 index 0000000000..1676b0ce1c --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp @@ -0,0 +1,50 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-2016 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 "GL45Backend.h" +#include "../gl/GLBuffer.h" + +using namespace gpu; +using namespace gpu::gl45; + +class GL45Buffer : public gl::GLBuffer { + using Parent = gpu::gl::GLBuffer; + static GLuint allocate() { + GLuint result; + glCreateBuffers(1, &result); + return result; + } + +public: + GL45Buffer(const Buffer& buffer, GLBuffer* original) : Parent(buffer, allocate()) { + glNamedBufferStorage(_buffer, _size, nullptr, GL_DYNAMIC_STORAGE_BIT); + if (original && original->_size) { + glCopyNamedBufferSubData(original->_buffer, _buffer, 0, 0, std::min(original->_size, _size)); + } + Backend::setGPUObject(buffer, this); + } + + void transfer() override { + Size offset; + Size size; + Size currentPage { 0 }; + auto data = _gpuObject.getSysmem().readData(); + while (_gpuObject.getNextTransferBlock(offset, size, currentPage)) { + glNamedBufferSubData(_buffer, (GLintptr)offset, (GLsizeiptr)size, data + offset); + } + (void)CHECK_GL_ERROR(); + _gpuObject._flags &= ~Buffer::DIRTY; + } +}; + +GLuint GL45Backend::getBufferID(const Buffer& buffer) { + return GL45Buffer::getId(buffer); +} + +gl::GLBuffer* GL45Backend::syncGPUObject(const Buffer& buffer) { + return GL45Buffer::sync(buffer); +} diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp new file mode 100644 index 0000000000..0fdd14dc84 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -0,0 +1,105 @@ +// +// GL45BackendInput.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 3/8/2015. +// 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 "GL45Backend.h" +#include "../gl/GLShared.h" + +using namespace gpu; +using namespace gpu::gl45; + +void GL45Backend::updateInput() { + + if (_input._invalidFormat) { + + InputStageState::ActivationCache newActivation; + + // Assign the vertex format required + if (_input._format) { + _input._attribBindingBuffers.reset(); + + const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); + auto& inputChannels = _input._format->getChannels(); + for (auto& channelIt : inputChannels) { + auto bufferChannelNum = (channelIt).first; + const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; + _input._attribBindingBuffers.set(bufferChannelNum); + + GLuint frequency = 0; + for (unsigned int i = 0; i < channel._slots.size(); i++) { + const Stream::Attribute& attrib = attributes.at(channel._slots[i]); + + GLuint slot = attrib._slot; + GLuint count = attrib._element.getLocationScalarCount(); + uint8_t locationCount = attrib._element.getLocationCount(); + GLenum type = gl::ELEMENT_TYPE_TO_GL[attrib._element.getType()]; + + GLuint offset = (GLuint)attrib._offset;; + GLboolean isNormalized = attrib._element.isNormalized(); + + GLenum perLocationSize = attrib._element.getLocationSize(); + for (GLuint locNum = 0; locNum < locationCount; ++locNum) { + GLuint attriNum = (GLuint)(slot + locNum); + newActivation.set(attriNum); + if (!_input._attributeActivation[attriNum]) { + _input._attributeActivation.set(attriNum); + glEnableVertexAttribArray(attriNum); + } + glVertexAttribFormat(attriNum, count, type, isNormalized, offset + locNum * perLocationSize); + // TODO: Support properly the IAttrib version + glVertexAttribBinding(attriNum, attrib._channel); + } + + if (i == 0) { + frequency = attrib._frequency; + } else { + assert(frequency == attrib._frequency); + } + + (void)CHECK_GL_ERROR(); + } + glVertexBindingDivisor(bufferChannelNum, frequency); + } + + + // Manage Activation what was and what is expected now + // This should only disable VertexAttribs since the one in use have been disabled above + for (GLuint i = 0; i < (GLuint)newActivation.size(); i++) { + bool newState = newActivation[i]; + if (newState != _input._attributeActivation[i]) { + if (newState) { + glEnableVertexAttribArray(i); + } else { + glDisableVertexAttribArray(i); + } + _input._attributeActivation.flip(i); + } + } + (void)CHECK_GL_ERROR(); + } + + _input._invalidFormat = false; + _stats._ISNumFormatChanges++; + } + + if (_input._invalidBuffers.any()) { + auto vbo = _input._bufferVBOs.data(); + auto offset = _input._bufferOffsets.data(); + auto stride = _input._bufferStrides.data(); + + for (GLuint buffer = 0; buffer < _input._buffers.size(); buffer++, vbo++, offset++, stride++) { + if (_input._invalidBuffers.test(buffer)) { + glBindVertexBuffer(buffer, (*vbo), (*offset), (GLsizei)(*stride)); + } + } + + _input._invalidBuffers.reset(); + (void)CHECK_GL_ERROR(); + } +} diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp new file mode 100644 index 0000000000..b846dd4df3 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp @@ -0,0 +1,145 @@ +// +// GL45BackendTexture.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 1/19/2015. +// 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 "GL45Backend.h" +#include "../gl/GLFramebuffer.h" +#include "../gl/GLTexture.h" + +#include + +namespace gpu { namespace gl45 { + +class GL45Framebuffer : public gl::GLFramebuffer { + using Parent = gl::GLFramebuffer; + static GLuint allocate() { + GLuint result; + glCreateFramebuffers(1, &result); + return result; + } +public: + void update() override { + gl::GLTexture* gltexture = nullptr; + TexturePointer surface; + if (_gpuObject.getColorStamps() != _colorStamps) { + if (_gpuObject.hasColor()) { + _colorBuffers.clear(); + static const GLenum colorAttachments[] = { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3, + GL_COLOR_ATTACHMENT4, + GL_COLOR_ATTACHMENT5, + GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT7, + GL_COLOR_ATTACHMENT8, + GL_COLOR_ATTACHMENT9, + GL_COLOR_ATTACHMENT10, + GL_COLOR_ATTACHMENT11, + GL_COLOR_ATTACHMENT12, + GL_COLOR_ATTACHMENT13, + GL_COLOR_ATTACHMENT14, + GL_COLOR_ATTACHMENT15 }; + + int unit = 0; + for (auto& b : _gpuObject.getRenderBuffers()) { + surface = b._texture; + if (surface) { + gltexture = gl::GLTexture::sync(surface, false); // Grab the gltexture and don't transfer + } else { + gltexture = nullptr; + } + + if (gltexture) { + glNamedFramebufferTexture(_id, colorAttachments[unit], gltexture->_texture, 0); + _colorBuffers.push_back(colorAttachments[unit]); + } else { + glNamedFramebufferTexture(_id, colorAttachments[unit], 0, 0); + } + unit++; + } + } + _colorStamps = _gpuObject.getColorStamps(); + } + + GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT; + if (!_gpuObject.hasStencil()) { + attachement = GL_DEPTH_ATTACHMENT; + } else if (!_gpuObject.hasDepth()) { + attachement = GL_STENCIL_ATTACHMENT; + } + + if (_gpuObject.getDepthStamp() != _depthStamp) { + auto surface = _gpuObject.getDepthStencilBuffer(); + if (_gpuObject.hasDepthStencil() && surface) { + gltexture = gl::GLTexture::sync(surface, false); // Grab the gltexture and don't transfer + } + + if (gltexture) { + glNamedFramebufferTexture(_id, attachement, gltexture->_texture, 0); + } else { + glNamedFramebufferTexture(_id, attachement, 0, 0); + } + _depthStamp = _gpuObject.getDepthStamp(); + } + + + // Last but not least, define where we draw + if (!_colorBuffers.empty()) { + glNamedFramebufferDrawBuffers(_id, (GLsizei)_colorBuffers.size(), _colorBuffers.data()); + } else { + glNamedFramebufferDrawBuffer(_id, GL_NONE); + } + + // Now check for completness + _status = glCheckNamedFramebufferStatus(_id, GL_DRAW_FRAMEBUFFER); + + // restore the current framebuffer + checkStatus(GL_DRAW_FRAMEBUFFER); + } + + +public: + GL45Framebuffer(const gpu::Framebuffer& framebuffer) + : Parent(framebuffer, allocate()) { } +}; + +gl::GLFramebuffer* GL45Backend::syncGPUObject(const Framebuffer& framebuffer) { + return gl::GLFramebuffer::sync(framebuffer); +} + +GLuint GL45Backend::getFramebufferID(const FramebufferPointer& framebuffer) { + return framebuffer ? gl::GLFramebuffer::getId(*framebuffer) : 0; +} + +void GL45Backend::do_blit(Batch& batch, size_t paramOffset) { + auto srcframebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); + Vec4i srcvp; + for (auto i = 0; i < 4; ++i) { + srcvp[i] = batch._params[paramOffset + 1 + i]._int; + } + + auto dstframebuffer = batch._framebuffers.get(batch._params[paramOffset + 5]._uint); + Vec4i dstvp; + for (auto i = 0; i < 4; ++i) { + dstvp[i] = batch._params[paramOffset + 6 + i]._int; + } + + // Assign dest framebuffer if not bound already + auto destFbo = getFramebufferID(dstframebuffer); + auto srcFbo = getFramebufferID(srcframebuffer); + glBlitNamedFramebuffer(srcFbo, destFbo, + srcvp.x, srcvp.y, srcvp.z, srcvp.w, + dstvp.x, dstvp.y, dstvp.z, dstvp.w, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + (void) CHECK_GL_ERROR(); +} + +} } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendQuery.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendQuery.cpp new file mode 100644 index 0000000000..9e808aeb82 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendQuery.cpp @@ -0,0 +1,38 @@ +// +// GL45BackendQuery.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 7/7/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 +// +#include "GL45Backend.h" + +#include "../gl/GLQuery.h" + +namespace gpu { namespace gl45 { + +class GL45Query : public gpu::gl::GLQuery { + using Parent = gpu::gl::GLQuery; +public: + static GLuint allocateQuery() { + GLuint result; + glCreateQueries(GL_TIMESTAMP, 1, &result); + return result; + } + + GL45Query(const Query& query) + : Parent(query, allocateQuery(), allocateQuery()){} +}; + +gl::GLQuery* GL45Backend::syncGPUObject(const Query& query) { + return GL45Query::sync(query); +} + +GLuint GL45Backend::getQueryID(const QueryPointer& query) { + return GL45Query::getId(query); +} + +} } \ No newline at end of file diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp new file mode 100644 index 0000000000..36fb4bfde3 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -0,0 +1,181 @@ +// +// GL45BackendTexture.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 1/19/2015. +// 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 "GL45Backend.h" + +#include +#include +#include + +#include "../gl/GLTexelFormat.h" + +using namespace gpu; +using namespace gpu::gl45; + +using GLTexelFormat = gl::GLTexelFormat; +using GL45Texture = GL45Backend::GL45Texture; + +GLuint GL45Texture::allocate(const Texture& texture) { + Backend::incrementTextureGPUCount(); + GLuint result; + glCreateTextures(getGLTextureType(texture), 1, &result); + return result; +} + +GLuint GL45Backend::getTextureID(const TexturePointer& texture, bool transfer) { + return GL45Texture::getId(texture, transfer); +} + +gl::GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texture, bool transfer) { + return GL45Texture::sync(texture, transfer); +} + +GL45Backend::GL45Texture::GL45Texture(const Texture& texture, bool transferrable) + : gl::GLTexture(texture, allocate(texture), transferrable) {} + +GL45Backend::GL45Texture::GL45Texture(const Texture& texture, GLTexture* original) + : gl::GLTexture(texture, allocate(texture), original) {} + +void GL45Backend::GL45Texture::withPreservedTexture(std::function f) const { + f(); +} + +void GL45Backend::GL45Texture::generateMips() const { + glGenerateTextureMipmap(_id); + (void)CHECK_GL_ERROR(); +} + +void GL45Backend::GL45Texture::allocateStorage() const { + gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); + glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); + if (_gpuObject.getTexelFormat().isCompressed()) { + qFatal("Compressed textures not yet supported"); + } + // Get the dimensions, accounting for the downgrade level + Vec3u dimensions = _gpuObject.evalMipDimensions(_minMip); + glTextureStorage2D(_id, usedMipLevels(), texelFormat.internalFormat, dimensions.x, dimensions.y); + (void)CHECK_GL_ERROR(); +} + +void GL45Backend::GL45Texture::updateSize() const { + setSize(_virtualSize); + if (!_id) { + return; + } + + if (_gpuObject.getTexelFormat().isCompressed()) { + qFatal("Compressed textures not yet supported"); + } +} + +// Move content bits from the CPU to the GPU for a given mip / face +void GL45Backend::GL45Texture::transferMip(uint16_t mipLevel, uint8_t face) const { + auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); + gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); + auto size = _gpuObject.evalMipDimensions(mipLevel); + if (GL_TEXTURE_2D == _target) { + glTextureSubImage2D(_id, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); + } else if (GL_TEXTURE_CUBE_MAP == _target) { + glTextureSubImage3D(_id, mipLevel, 0, 0, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, mip->readData()); + } else { + Q_ASSERT(false); + } + (void)CHECK_GL_ERROR(); +} + +// This should never happen on the main thread +// Move content bits from the CPU to the GPU +void GL45Backend::GL45Texture::transfer() const { + PROFILE_RANGE(__FUNCTION__); + //qDebug() << "Transferring texture: " << _privateTexture; + // Need to update the content of the GPU object from the source sysmem of the texture + if (_contentStamp >= _gpuObject.getDataStamp()) { + return; + } + + if (_downsampleSource._texture) { + GLuint fbo { 0 }; + glCreateFramebuffers(1, &fbo); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + // Find the distance between the old min mip and the new one + uint16 mipOffset = _minMip - _downsampleSource._minMip; + for (uint16 i = _minMip; i <= _maxMip; ++i) { + uint16 targetMip = i - _minMip; + uint16 sourceMip = targetMip + mipOffset; + Vec3u dimensions = _gpuObject.evalMipDimensions(i); + for (GLenum target : getFaceTargets(_target)) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, _downsampleSource._texture, sourceMip); + (void)CHECK_GL_ERROR(); + glCopyTextureSubImage2D(_id, targetMip, 0, 0, 0, 0, dimensions.x, dimensions.y); + (void)CHECK_GL_ERROR(); + } + } + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + } else { + // GO through the process of allocating the correct storage and/or update the content + switch (_gpuObject.getType()) { + case Texture::TEX_2D: + { + for (uint16_t i = _minMip; i <= _maxMip; ++i) { + if (_gpuObject.isStoredMipFaceAvailable(i)) { + transferMip(i); + } + } + } + break; + + case Texture::TEX_CUBE: + // transfer pixels from each faces + for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuObject.isStoredMipFaceAvailable(i, f)) { + transferMip(i, f); + } + } + } + break; + + default: + qCWarning(gpugl45logging) << __FUNCTION__ << " case for Texture Type " << _gpuObject.getType() << " not supported"; + break; + } + } + if (_gpuObject.isAutogenerateMips()) { + glGenerateTextureMipmap(_id); + (void)CHECK_GL_ERROR(); + } +} + +void GL45Backend::GL45Texture::syncSampler() const { + const Sampler& sampler = _gpuObject.getSampler(); + + const auto& fm = FILTER_MODES[sampler.getFilter()]; + glTextureParameteri(_id, GL_TEXTURE_MIN_FILTER, fm.minFilter); + glTextureParameteri(_id, GL_TEXTURE_MAG_FILTER, fm.magFilter); + + if (sampler.doComparison()) { + glTextureParameteri(_id, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + glTextureParameteri(_id, GL_TEXTURE_COMPARE_FUNC, gl::COMPARISON_TO_GL[sampler.getComparisonFunction()]); + } else { + glTextureParameteri(_id, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + + glTextureParameteri(_id, GL_TEXTURE_WRAP_S, WRAP_MODES[sampler.getWrapModeU()]); + glTextureParameteri(_id, GL_TEXTURE_WRAP_T, WRAP_MODES[sampler.getWrapModeV()]); + glTextureParameteri(_id, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]); + glTextureParameterfv(_id, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset()); + glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); + glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); + glTextureParameterf(_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); +} + diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp new file mode 100644 index 0000000000..96afb4cc71 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp @@ -0,0 +1,71 @@ +// +// GL45BackendTransform.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 3/8/2015. +// 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 "GL45Backend.h" + +using namespace gpu; +using namespace gpu::gl45; + +void GL45Backend::initTransform() { + GLuint transformBuffers[3]; + glCreateBuffers(3, transformBuffers); + _transform._objectBuffer = transformBuffers[0]; + _transform._cameraBuffer = transformBuffers[1]; + _transform._drawCallInfoBuffer = transformBuffers[2]; + glCreateTextures(GL_TEXTURE_BUFFER, 1, &_transform._objectBufferTexture); + size_t cameraSize = sizeof(TransformStageState::CameraBufferElement); + while (_transform._cameraUboSize < cameraSize) { + _transform._cameraUboSize += _uboAlignment; + } +} + +void GL45Backend::transferTransformState(const Batch& batch) const { + // FIXME not thread safe + static std::vector bufferData; + if (!_transform._cameras.empty()) { + bufferData.resize(_transform._cameraUboSize * _transform._cameras.size()); + for (size_t i = 0; i < _transform._cameras.size(); ++i) { + memcpy(bufferData.data() + (_transform._cameraUboSize * i), &_transform._cameras[i], sizeof(TransformStageState::CameraBufferElement)); + } + glNamedBufferData(_transform._cameraBuffer, bufferData.size(), bufferData.data(), GL_STREAM_DRAW); + } + + if (!batch._objects.empty()) { + auto byteSize = batch._objects.size() * sizeof(Batch::TransformObject); + bufferData.resize(byteSize); + memcpy(bufferData.data(), batch._objects.data(), byteSize); + glNamedBufferData(_transform._objectBuffer, bufferData.size(), bufferData.data(), GL_STREAM_DRAW); + } + + if (!batch._namedData.empty()) { + bufferData.clear(); + for (auto& data : batch._namedData) { + auto currentSize = bufferData.size(); + auto bytesToCopy = data.second.drawCallInfos.size() * sizeof(Batch::DrawCallInfo); + bufferData.resize(currentSize + bytesToCopy); + memcpy(bufferData.data() + currentSize, data.second.drawCallInfos.data(), bytesToCopy); + _transform._drawCallInfoOffsets[data.first] = (GLvoid*)currentSize; + } + glNamedBufferData(_transform._drawCallInfoBuffer, bufferData.size(), bufferData.data(), GL_STREAM_DRAW); + } + +#ifdef GPU_SSBO_DRAW_CALL_INFO + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, TRANSFORM_OBJECT_SLOT, _transform._objectBuffer); +#else + glTextureBuffer(_transform._objectBufferTexture, GL_RGBA32F, _transform._objectBuffer); + glActiveTexture(GL_TEXTURE0 + TRANSFORM_OBJECT_SLOT); + glBindTexture(GL_TEXTURE_BUFFER, _transform._objectBufferTexture); +#endif + + CHECK_GL_ERROR(); + + // Make sure the current Camera offset is unknown before render Draw + _transform._currentCameraOffset = INVALID_OFFSET; +} diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index ae1e9b4427..ecddeb07ad 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -2,3 +2,5 @@ set(TARGET_NAME gpu) AUTOSCRIBE_SHADER_LIB(gpu) setup_hifi_library() link_hifi_libraries(shared) + +target_nsight() diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 18e923831f..9161ee3642 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -10,9 +10,10 @@ // #include "Batch.h" -#include #include +#include + #if defined(NSIGHT_FOUND) #include "nvToolsExt.h" @@ -25,28 +26,43 @@ ProfileRangeBatch::~ProfileRangeBatch() { } #endif -#define ADD_COMMAND(call) _commands.push_back(COMMAND_##call); _commandOffsets.push_back(_params.size()); +#define ADD_COMMAND(call) _commands.emplace_back(COMMAND_##call); _commandOffsets.emplace_back(_params.size()); using namespace gpu; -Batch::Batch(const CacheState& cacheState) : Batch() { - _commands.reserve(cacheState.commandsSize); - _commandOffsets.reserve(cacheState.offsetsSize); - _params.reserve(cacheState.paramsSize); - _data.reserve(cacheState.dataSize); -} +size_t Batch::_commandsMax { BATCH_PREALLOCATE_MIN }; +size_t Batch::_commandOffsetsMax { BATCH_PREALLOCATE_MIN }; +size_t Batch::_paramsMax { BATCH_PREALLOCATE_MIN }; +size_t Batch::_dataMax { BATCH_PREALLOCATE_MIN }; +size_t Batch::_objectsMax { BATCH_PREALLOCATE_MIN }; +size_t Batch::_drawCallInfosMax { BATCH_PREALLOCATE_MIN }; -Batch::CacheState Batch::getCacheState() { - return CacheState(_commands.size(), _commandOffsets.size(), _params.size(), _data.size(), - _buffers.size(), _textures.size(), _streamFormats.size(), _transforms.size(), _pipelines.size(), - _framebuffers.size(), _queries.size()); +Batch::Batch() { + _commands.reserve(_commandsMax); + _commandOffsets.reserve(_commandOffsetsMax); + _params.reserve(_paramsMax); + _data.reserve(_dataMax); + _objects.reserve(_objectsMax); + _drawCallInfos.reserve(_drawCallInfosMax); } Batch::~Batch() { - //qDebug() << "Batch::~Batch()... " << getCacheState(); + _commandsMax = std::max(_commands.size(), _commandsMax); + _commandOffsetsMax = std::max(_commandOffsets.size(), _commandOffsetsMax); + _paramsMax = std::max(_params.size(), _paramsMax); + _dataMax = std::max(_data.size(), _dataMax); + _objectsMax = std::max(_objects.size(), _objectsMax); + _drawCallInfosMax = std::max(_drawCallInfos.size(), _drawCallInfosMax); } void Batch::clear() { + _commandsMax = std::max(_commands.size(), _commandsMax); + _commandOffsetsMax = std::max(_commandOffsets.size(), _commandOffsetsMax); + _paramsMax = std::max(_params.size(), _paramsMax); + _dataMax = std::max(_data.size(), _dataMax); + _objectsMax = std::max(_objects.size(), _objectsMax); + _drawCallInfosMax = std::max(_drawCallInfos.size(), _drawCallInfosMax); + _commands.clear(); _commandOffsets.clear(); _params.clear(); @@ -57,6 +73,8 @@ void Batch::clear() { _transforms.clear(); _pipelines.clear(); _framebuffers.clear(); + _objects.clear(); + _drawCallInfos.clear(); } size_t Batch::cacheData(size_t size, const void* data) { @@ -71,9 +89,9 @@ size_t Batch::cacheData(size_t size, const void* data) { void Batch::draw(Primitive primitiveType, uint32 numVertices, uint32 startVertex) { ADD_COMMAND(draw); - _params.push_back(startVertex); - _params.push_back(numVertices); - _params.push_back(primitiveType); + _params.emplace_back(startVertex); + _params.emplace_back(numVertices); + _params.emplace_back(primitiveType); captureDrawCallInfo(); } @@ -81,9 +99,9 @@ void Batch::draw(Primitive primitiveType, uint32 numVertices, uint32 startVertex void Batch::drawIndexed(Primitive primitiveType, uint32 numIndices, uint32 startIndex) { ADD_COMMAND(drawIndexed); - _params.push_back(startIndex); - _params.push_back(numIndices); - _params.push_back(primitiveType); + _params.emplace_back(startIndex); + _params.emplace_back(numIndices); + _params.emplace_back(primitiveType); captureDrawCallInfo(); } @@ -91,11 +109,11 @@ void Batch::drawIndexed(Primitive primitiveType, uint32 numIndices, uint32 start void Batch::drawInstanced(uint32 numInstances, Primitive primitiveType, uint32 numVertices, uint32 startVertex, uint32 startInstance) { ADD_COMMAND(drawInstanced); - _params.push_back(startInstance); - _params.push_back(startVertex); - _params.push_back(numVertices); - _params.push_back(primitiveType); - _params.push_back(numInstances); + _params.emplace_back(startInstance); + _params.emplace_back(startVertex); + _params.emplace_back(numVertices); + _params.emplace_back(primitiveType); + _params.emplace_back(numInstances); captureDrawCallInfo(); } @@ -103,11 +121,11 @@ void Batch::drawInstanced(uint32 numInstances, Primitive primitiveType, uint32 n void Batch::drawIndexedInstanced(uint32 numInstances, Primitive primitiveType, uint32 numIndices, uint32 startIndex, uint32 startInstance) { ADD_COMMAND(drawIndexedInstanced); - _params.push_back(startInstance); - _params.push_back(startIndex); - _params.push_back(numIndices); - _params.push_back(primitiveType); - _params.push_back(numInstances); + _params.emplace_back(startInstance); + _params.emplace_back(startIndex); + _params.emplace_back(numIndices); + _params.emplace_back(primitiveType); + _params.emplace_back(numInstances); captureDrawCallInfo(); } @@ -115,16 +133,16 @@ void Batch::drawIndexedInstanced(uint32 numInstances, Primitive primitiveType, u void Batch::multiDrawIndirect(uint32 numCommands, Primitive primitiveType) { ADD_COMMAND(multiDrawIndirect); - _params.push_back(numCommands); - _params.push_back(primitiveType); + _params.emplace_back(numCommands); + _params.emplace_back(primitiveType); captureDrawCallInfo(); } void Batch::multiDrawIndexedIndirect(uint32 nbCommands, Primitive primitiveType) { ADD_COMMAND(multiDrawIndexedIndirect); - _params.push_back(nbCommands); - _params.push_back(primitiveType); + _params.emplace_back(nbCommands); + _params.emplace_back(primitiveType); captureDrawCallInfo(); } @@ -132,16 +150,16 @@ void Batch::multiDrawIndexedIndirect(uint32 nbCommands, Primitive primitiveType) void Batch::setInputFormat(const Stream::FormatPointer& format) { ADD_COMMAND(setInputFormat); - _params.push_back(_streamFormats.cache(format)); + _params.emplace_back(_streamFormats.cache(format)); } void Batch::setInputBuffer(Slot channel, const BufferPointer& buffer, Offset offset, Offset stride) { ADD_COMMAND(setInputBuffer); - _params.push_back(stride); - _params.push_back(offset); - _params.push_back(_buffers.cache(buffer)); - _params.push_back(channel); + _params.emplace_back(stride); + _params.emplace_back(offset); + _params.emplace_back(_buffers.cache(buffer)); + _params.emplace_back(channel); } void Batch::setInputBuffer(Slot channel, const BufferView& view) { @@ -162,9 +180,9 @@ void Batch::setInputStream(Slot startChannel, const BufferStream& stream) { void Batch::setIndexBuffer(Type type, const BufferPointer& buffer, Offset offset) { ADD_COMMAND(setIndexBuffer); - _params.push_back(offset); - _params.push_back(_buffers.cache(buffer)); - _params.push_back(type); + _params.emplace_back(offset); + _params.emplace_back(_buffers.cache(buffer)); + _params.emplace_back(type); } void Batch::setIndexBuffer(const BufferView& buffer) { @@ -174,9 +192,9 @@ void Batch::setIndexBuffer(const BufferView& buffer) { void Batch::setIndirectBuffer(const BufferPointer& buffer, Offset offset, Offset stride) { ADD_COMMAND(setIndirectBuffer); - _params.push_back(_buffers.cache(buffer)); - _params.push_back(offset); - _params.push_back(stride); + _params.emplace_back(_buffers.cache(buffer)); + _params.emplace_back(offset); + _params.emplace_back(stride); } @@ -190,56 +208,56 @@ void Batch::setModelTransform(const Transform& model) { void Batch::setViewTransform(const Transform& view) { ADD_COMMAND(setViewTransform); - _params.push_back(_transforms.cache(view)); + _params.emplace_back(_transforms.cache(view)); } void Batch::setProjectionTransform(const Mat4& proj) { ADD_COMMAND(setProjectionTransform); - _params.push_back(cacheData(sizeof(Mat4), &proj)); + _params.emplace_back(cacheData(sizeof(Mat4), &proj)); } void Batch::setViewportTransform(const Vec4i& viewport) { ADD_COMMAND(setViewportTransform); - _params.push_back(cacheData(sizeof(Vec4i), &viewport)); + _params.emplace_back(cacheData(sizeof(Vec4i), &viewport)); } void Batch::setDepthRangeTransform(float nearDepth, float farDepth) { ADD_COMMAND(setDepthRangeTransform); - _params.push_back(farDepth); - _params.push_back(nearDepth); + _params.emplace_back(farDepth); + _params.emplace_back(nearDepth); } void Batch::setPipeline(const PipelinePointer& pipeline) { ADD_COMMAND(setPipeline); - _params.push_back(_pipelines.cache(pipeline)); + _params.emplace_back(_pipelines.cache(pipeline)); } void Batch::setStateBlendFactor(const Vec4& factor) { ADD_COMMAND(setStateBlendFactor); - _params.push_back(factor.x); - _params.push_back(factor.y); - _params.push_back(factor.z); - _params.push_back(factor.w); + _params.emplace_back(factor.x); + _params.emplace_back(factor.y); + _params.emplace_back(factor.z); + _params.emplace_back(factor.w); } void Batch::setStateScissorRect(const Vec4i& rect) { ADD_COMMAND(setStateScissorRect); - _params.push_back(cacheData(sizeof(Vec4i), &rect)); + _params.emplace_back(cacheData(sizeof(Vec4i), &rect)); } void Batch::setUniformBuffer(uint32 slot, const BufferPointer& buffer, Offset offset, Offset size) { ADD_COMMAND(setUniformBuffer); - _params.push_back(size); - _params.push_back(offset); - _params.push_back(_buffers.cache(buffer)); - _params.push_back(slot); + _params.emplace_back(size); + _params.emplace_back(offset); + _params.emplace_back(_buffers.cache(buffer)); + _params.emplace_back(slot); } void Batch::setUniformBuffer(uint32 slot, const BufferView& view) { @@ -250,8 +268,8 @@ void Batch::setUniformBuffer(uint32 slot, const BufferView& view) { void Batch::setResourceTexture(uint32 slot, const TexturePointer& texture) { ADD_COMMAND(setResourceTexture); - _params.push_back(_textures.cache(texture)); - _params.push_back(slot); + _params.emplace_back(_textures.cache(texture)); + _params.emplace_back(slot); } void Batch::setResourceTexture(uint32 slot, const TextureView& view) { @@ -261,21 +279,21 @@ void Batch::setResourceTexture(uint32 slot, const TextureView& view) { void Batch::setFramebuffer(const FramebufferPointer& framebuffer) { ADD_COMMAND(setFramebuffer); - _params.push_back(_framebuffers.cache(framebuffer)); + _params.emplace_back(_framebuffers.cache(framebuffer)); } void Batch::clearFramebuffer(Framebuffer::Masks targets, const Vec4& color, float depth, int stencil, bool enableScissor) { ADD_COMMAND(clearFramebuffer); - _params.push_back(enableScissor); - _params.push_back(stencil); - _params.push_back(depth); - _params.push_back(color.w); - _params.push_back(color.z); - _params.push_back(color.y); - _params.push_back(color.x); - _params.push_back(targets); + _params.emplace_back(enableScissor); + _params.emplace_back(stencil); + _params.emplace_back(depth); + _params.emplace_back(color.w); + _params.emplace_back(color.z); + _params.emplace_back(color.y); + _params.emplace_back(color.x); + _params.emplace_back(targets); } void Batch::clearColorFramebuffer(Framebuffer::Masks targets, const Vec4& color, bool enableScissor) { @@ -298,40 +316,40 @@ void Batch::blit(const FramebufferPointer& src, const Vec4i& srcViewport, const FramebufferPointer& dst, const Vec4i& dstViewport) { ADD_COMMAND(blit); - _params.push_back(_framebuffers.cache(src)); - _params.push_back(srcViewport.x); - _params.push_back(srcViewport.y); - _params.push_back(srcViewport.z); - _params.push_back(srcViewport.w); - _params.push_back(_framebuffers.cache(dst)); - _params.push_back(dstViewport.x); - _params.push_back(dstViewport.y); - _params.push_back(dstViewport.z); - _params.push_back(dstViewport.w); + _params.emplace_back(_framebuffers.cache(src)); + _params.emplace_back(srcViewport.x); + _params.emplace_back(srcViewport.y); + _params.emplace_back(srcViewport.z); + _params.emplace_back(srcViewport.w); + _params.emplace_back(_framebuffers.cache(dst)); + _params.emplace_back(dstViewport.x); + _params.emplace_back(dstViewport.y); + _params.emplace_back(dstViewport.z); + _params.emplace_back(dstViewport.w); } void Batch::generateTextureMips(const TexturePointer& texture) { ADD_COMMAND(generateTextureMips); - _params.push_back(_textures.cache(texture)); + _params.emplace_back(_textures.cache(texture)); } void Batch::beginQuery(const QueryPointer& query) { ADD_COMMAND(beginQuery); - _params.push_back(_queries.cache(query)); + _params.emplace_back(_queries.cache(query)); } void Batch::endQuery(const QueryPointer& query) { ADD_COMMAND(endQuery); - _params.push_back(_queries.cache(query)); + _params.emplace_back(_queries.cache(query)); } void Batch::getQuery(const QueryPointer& query) { ADD_COMMAND(getQuery); - _params.push_back(_queries.cache(query)); + _params.emplace_back(_queries.cache(query)); } void Batch::resetStages() { @@ -340,12 +358,12 @@ void Batch::resetStages() { void Batch::runLambda(std::function f) { ADD_COMMAND(runLambda); - _params.push_back(_lambdas.cache(f)); + _params.emplace_back(_lambdas.cache(f)); } void Batch::startNamedCall(const std::string& name) { ADD_COMMAND(startNamedCall); - _params.push_back(_names.cache(name)); + _params.emplace_back(_names.cache(name)); _currentNamedCall = name; } @@ -421,14 +439,14 @@ void Batch::captureDrawCallInfoImpl() { //_model.getInverseMatrix(_object._modelInverse); object._modelInverse = glm::inverse(object._model); - _objects.push_back(object); + _objects.emplace_back(object); // Flag is clean _invalidModel = false; } auto& drawCallInfos = getDrawCallInfoBuffer(); - drawCallInfos.push_back((uint16)_objects.size() - 1); + drawCallInfos.emplace_back((uint16)_objects.size() - 1); } void Batch::captureDrawCallInfo() { @@ -457,22 +475,11 @@ void Batch::preExecute() { } } -QDebug& operator<<(QDebug& debug, const Batch::CacheState& cacheState) { - debug << "Batch::CacheState[ " - << "commandsSize:" << cacheState.commandsSize - << "offsetsSize:" << cacheState.offsetsSize - << "paramsSize:" << cacheState.paramsSize - << "dataSize:" << cacheState.dataSize - << "]"; - - return debug; -} - // Debugging void Batch::pushProfileRange(const char* name) { #if defined(NSIGHT_FOUND) ADD_COMMAND(pushProfileRange); - _params.push_back(_profileRanges.cache(name)); + _params.emplace_back(_profileRanges.cache(name)); #endif } @@ -481,3 +488,116 @@ void Batch::popProfileRange() { ADD_COMMAND(popProfileRange); #endif } + +#define GL_TEXTURE0 0x84C0 + +void Batch::_glActiveBindTexture(uint32 unit, uint32 target, uint32 texture) { + // clean the cache on the texture unit we are going to use so the next call to setResourceTexture() at the same slot works fine + setResourceTexture(unit - GL_TEXTURE0, nullptr); + + ADD_COMMAND(glActiveBindTexture); + _params.emplace_back(texture); + _params.emplace_back(target); + _params.emplace_back(unit); +} + +void Batch::_glUniform1i(int32 location, int32 v0) { + if (location < 0) { + return; + } + ADD_COMMAND(glUniform1i); + _params.emplace_back(v0); + _params.emplace_back(location); +} + +void Batch::_glUniform1f(int32 location, float v0) { + if (location < 0) { + return; + } + ADD_COMMAND(glUniform1f); + _params.emplace_back(v0); + _params.emplace_back(location); +} + +void Batch::_glUniform2f(int32 location, float v0, float v1) { + ADD_COMMAND(glUniform2f); + + _params.emplace_back(v1); + _params.emplace_back(v0); + _params.emplace_back(location); +} + +void Batch::_glUniform3f(int32 location, float v0, float v1, float v2) { + ADD_COMMAND(glUniform3f); + + _params.emplace_back(v2); + _params.emplace_back(v1); + _params.emplace_back(v0); + _params.emplace_back(location); +} + +void Batch::_glUniform4f(int32 location, float v0, float v1, float v2, float v3) { + ADD_COMMAND(glUniform4f); + + _params.emplace_back(v3); + _params.emplace_back(v2); + _params.emplace_back(v1); + _params.emplace_back(v0); + _params.emplace_back(location); +} + +void Batch::_glUniform3fv(int32 location, int count, const float* value) { + ADD_COMMAND(glUniform3fv); + + const int VEC3_SIZE = 3 * sizeof(float); + _params.emplace_back(cacheData(count * VEC3_SIZE, value)); + _params.emplace_back(count); + _params.emplace_back(location); +} + +void Batch::_glUniform4fv(int32 location, int count, const float* value) { + ADD_COMMAND(glUniform4fv); + + const int VEC4_SIZE = 4 * sizeof(float); + _params.emplace_back(cacheData(count * VEC4_SIZE, value)); + _params.emplace_back(count); + _params.emplace_back(location); +} + +void Batch::_glUniform4iv(int32 location, int count, const int32* value) { + ADD_COMMAND(glUniform4iv); + + const int VEC4_SIZE = 4 * sizeof(int); + _params.emplace_back(cacheData(count * VEC4_SIZE, value)); + _params.emplace_back(count); + _params.emplace_back(location); +} + +void Batch::_glUniformMatrix3fv(int32 location, int count, uint8 transpose, const float* value) { + ADD_COMMAND(glUniformMatrix3fv); + + const int MATRIX3_SIZE = 9 * sizeof(float); + _params.emplace_back(cacheData(count * MATRIX3_SIZE, value)); + _params.emplace_back(transpose); + _params.emplace_back(count); + _params.emplace_back(location); +} + +void Batch::_glUniformMatrix4fv(int32 location, int count, uint8 transpose, const float* value) { + ADD_COMMAND(glUniformMatrix4fv); + + const int MATRIX4_SIZE = 16 * sizeof(float); + _params.emplace_back(cacheData(count * MATRIX4_SIZE, value)); + _params.emplace_back(transpose); + _params.emplace_back(count); + _params.emplace_back(location); +} + +void Batch::_glColor4f(float red, float green, float blue, float alpha) { + ADD_COMMAND(glColor4f); + + _params.emplace_back(alpha); + _params.emplace_back(blue); + _params.emplace_back(green); + _params.emplace_back(red); +} diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index a2ee57759f..9cf1ca8269 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -25,7 +26,7 @@ #include "Transform.h" class QDebug; - +#define BATCH_PREALLOCATE_MIN 128 namespace gpu { enum ReservedSlot { @@ -86,6 +87,7 @@ public: using NamedBatchDataMap = std::map; DrawCallInfoBuffer _drawCallInfos; + static size_t _drawCallInfosMax; std::string _currentNamedCall; @@ -95,34 +97,7 @@ public: void captureDrawCallInfo(); void captureNamedDrawCallInfo(std::string name); - class CacheState { - public: - size_t commandsSize; - size_t offsetsSize; - size_t paramsSize; - size_t dataSize; - - size_t buffersSize; - size_t texturesSize; - size_t streamFormatsSize; - size_t transformsSize; - size_t pipelinesSize; - size_t framebuffersSize; - size_t queriesSize; - - CacheState() : commandsSize(0), offsetsSize(0), paramsSize(0), dataSize(0), buffersSize(0), texturesSize(0), - streamFormatsSize(0), transformsSize(0), pipelinesSize(0), framebuffersSize(0), queriesSize(0) { } - - CacheState(size_t commandsSize, size_t offsetsSize, size_t paramsSize, size_t dataSize, size_t buffersSize, - size_t texturesSize, size_t streamFormatsSize, size_t transformsSize, size_t pipelinesSize, - size_t framebuffersSize, size_t queriesSize) : - commandsSize(commandsSize), offsetsSize(offsetsSize), paramsSize(paramsSize), dataSize(dataSize), - buffersSize(buffersSize), texturesSize(texturesSize), streamFormatsSize(streamFormatsSize), - transformsSize(transformsSize), pipelinesSize(pipelinesSize), framebuffersSize(framebuffersSize), queriesSize(queriesSize) { } - }; - - Batch() {} - Batch(const CacheState& cacheState); + Batch(); explicit Batch(const Batch& batch); ~Batch(); @@ -130,9 +105,6 @@ public: void preExecute(); - CacheState getCacheState(); - - // Batches may need to override the context level stereo settings // if they're performing framebuffer copy operations, like the // deferred lighting resolution mechanism @@ -269,6 +241,7 @@ public: void _glUniform3fv(int location, int count, const float* value); void _glUniform4fv(int location, int count, const float* value); void _glUniform4iv(int location, int count, const int* value); + void _glUniformMatrix3fv(int location, int count, unsigned char transpose, const float* value); void _glUniformMatrix4fv(int location, int count, unsigned char transpose, const float* value); void _glUniform(int location, int v0) { @@ -291,6 +264,10 @@ public: _glUniform4f(location, v.x, v.y, v.z, v.w); } + void _glUniform(int location, const glm::mat3& v) { + _glUniformMatrix3fv(location, 1, false, glm::value_ptr(v)); + } + void _glColor4f(float red, float green, float blue, float alpha); enum Command { @@ -348,6 +325,7 @@ public: COMMAND_glUniform3fv, COMMAND_glUniform4fv, COMMAND_glUniform4iv, + COMMAND_glUniformMatrix3fv, COMMAND_glUniformMatrix4fv, COMMAND_glColor4f, @@ -394,15 +372,25 @@ public: typedef T Data; Data _data; Cache(const Data& data) : _data(data) {} + static size_t _max; class Vector { public: std::vector< Cache > _items; + Vector() { + _items.reserve(_max); + } + + ~Vector() { + _max = std::max(_items.size(), _max); + } + + size_t size() const { return _items.size(); } size_t cache(const Data& data) { size_t offset = _items.size(); - _items.push_back(Cache(data)); + _items.emplace_back(data); return offset; } @@ -442,11 +430,18 @@ public: } Commands _commands; - CommandOffsets _commandOffsets; - Params _params; - Bytes _data; + static size_t _commandsMax; - // SSBO class... layout MUST match the layout in TransformCamera.slh + CommandOffsets _commandOffsets; + static size_t _commandOffsetsMax; + + Params _params; + static size_t _paramsMax; + + Bytes _data; + static size_t _dataMax; + + // SSBO class... layout MUST match the layout in Transform.slh class TransformObject { public: Mat4 _model; @@ -457,6 +452,7 @@ public: bool _invalidModel { true }; Transform _currentModel; TransformObjects _objects; + static size_t _objectsMax; BufferCaches _buffers; TextureCaches _textures; @@ -484,6 +480,9 @@ protected: void captureDrawCallInfoImpl(); }; +template +size_t Batch::Cache::_max = BATCH_PREALLOCATE_MIN; + } #if defined(NSIGHT_FOUND) @@ -505,6 +504,4 @@ private: #endif -QDebug& operator<<(QDebug& debug, const gpu::Batch::CacheState& cacheState); - #endif diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index c8e379480d..2c27260331 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -91,6 +91,9 @@ const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived(const Mat4 viewUntranslated = _view; viewUntranslated[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f); _projectionViewUntranslated = _projection * viewUntranslated; + + _stereoInfo = Vec4(0.0f); + return *this; } @@ -104,7 +107,9 @@ Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const S } result._projection = _stereo._eyeProjections[eye]; result.recomputeDerived(offsetTransform); - + + result._stereoInfo = Vec4(1.0f, (float) eye, 0.0f, 0.0f); + return result; } diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index cead2c623d..2d0baa0497 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -95,7 +95,7 @@ public: virtual void syncCache() = 0; virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0; - // UBO class... layout MUST match the layout in TransformCamera.slh + // UBO class... layout MUST match the layout in Transform.slh class TransformCamera { public: mutable Mat4 _view; @@ -104,6 +104,7 @@ public: Mat4 _projection; mutable Mat4 _projectionInverse; Vec4 _viewport; // Public value is int but float in the shader to stay in floats for all the transform computations. + mutable Vec4 _stereoInfo; const Backend::TransformCamera& recomputeDerived(const Transform& xformView) const; TransformCamera getEyeCamera(int eye, const StereoState& stereo, const Transform& xformView) const; @@ -231,11 +232,9 @@ typedef std::shared_ptr ContextPointer; template void doInBatch(std::shared_ptr context, F f) { - static gpu::Batch::CacheState cacheState; - gpu::Batch batch(cacheState); + gpu::Batch batch; f(batch); context->render(batch); - cacheState = batch.getCacheState(); } }; diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index b7a8380f78..83b2a59a73 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -12,6 +12,7 @@ using namespace gpu; const Element Element::COLOR_RGBA_32{ VEC4, NUINT8, RGBA }; const Element Element::COLOR_SRGBA_32{ VEC4, NUINT8, SRGBA }; +const Element Element::COLOR_R11G11B10{ SCALAR, FLOAT, R11G11B10 }; const Element Element::VEC4F_COLOR_RGBA{ VEC4, FLOAT, RGBA }; const Element Element::VEC2F_UV{ VEC2, FLOAT, UV }; const Element Element::VEC2F_XY{ VEC2, FLOAT, XY }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 6b2bc4b93e..bec218d1fd 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -246,6 +246,7 @@ public: static const Element COLOR_RGBA_32; static const Element COLOR_SRGBA_32; + static const Element COLOR_R11G11B10; static const Element VEC4F_COLOR_RGBA; static const Element VEC2F_UV; static const Element VEC2F_XY; diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h index 621ccee2f9..7fe6739ff7 100644 --- a/libraries/gpu/src/gpu/Forward.h +++ b/libraries/gpu/src/gpu/Forward.h @@ -84,7 +84,14 @@ namespace gpu { namespace gl { class GLBuffer; - class GLBackend; + } + + namespace gl41 { + class GL41Backend; + } + + namespace gl45 { + class GL45Backend; } } diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp index ac7b136550..beb0334208 100755 --- a/libraries/gpu/src/gpu/Framebuffer.cpp +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -13,6 +13,8 @@ #include #include +#include + using namespace gpu; Framebuffer::~Framebuffer() { @@ -290,4 +292,29 @@ Format Framebuffer::getDepthStencilBufferFormat() const { } else { return _depthStencilBuffer._element; } -} \ No newline at end of file +} +glm::vec4 Framebuffer::evalSubregionTexcoordTransformCoefficients(const glm::ivec2& sourceSurface, const glm::ivec2& destRegionSize, const glm::ivec2& destRegionOffset) { + float sMin = destRegionOffset.x / (float)sourceSurface.x; + float sWidth = destRegionSize.x / (float)sourceSurface.x; + float tMin = destRegionOffset.y / (float)sourceSurface.y; + float tHeight = destRegionSize.y / (float)sourceSurface.y; + return glm::vec4(sMin, tMin, sWidth, tHeight); +} + +glm::vec4 Framebuffer::evalSubregionTexcoordTransformCoefficients(const glm::ivec2& sourceSurface, const glm::ivec4& destViewport) { + return evalSubregionTexcoordTransformCoefficients(sourceSurface, glm::ivec2(destViewport.z, destViewport.w), glm::ivec2(destViewport.x, destViewport.y)); +} + +Transform Framebuffer::evalSubregionTexcoordTransform(const glm::ivec2& sourceSurface, const glm::ivec2& destRegionSize, const glm::ivec2& destRegionOffset) { + float sMin = destRegionOffset.x / (float)sourceSurface.x; + float sWidth = destRegionSize.x / (float)sourceSurface.x; + float tMin = destRegionOffset.y / (float)sourceSurface.y; + float tHeight = destRegionSize.y / (float)sourceSurface.y; + Transform model; + model.setTranslation(glm::vec3(sMin, tMin, 0.0)); + model.setScale(glm::vec3(sWidth, tHeight, 1.0)); + return model; +} +Transform Framebuffer::evalSubregionTexcoordTransform(const glm::ivec2& sourceSurface, const glm::ivec4& destViewport) { + return evalSubregionTexcoordTransform(sourceSurface, glm::ivec2(destViewport.z, destViewport.w), glm::ivec2(destViewport.x, destViewport.y)); +} diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index 5d016645fe..6fa6367c7d 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -14,6 +14,8 @@ #include "Texture.h" #include +class Transform; // Texcood transform util + namespace gpu { typedef Element Format; @@ -139,6 +141,12 @@ public: Stamp getDepthStamp() const { return _depthStamp; } const std::vector& getColorStamps() const { return _colorStamps; } + static glm::vec4 evalSubregionTexcoordTransformCoefficients(const glm::ivec2& sourceSurface, const glm::ivec2& destRegionSize, const glm::ivec2& destRegionOffset = glm::ivec2(0)); + static glm::vec4 evalSubregionTexcoordTransformCoefficients(const glm::ivec2& sourceSurface, const glm::ivec4& destViewport); + + static Transform evalSubregionTexcoordTransform(const glm::ivec2& sourceSurface, const glm::ivec2& destRegionSize, const glm::ivec2& destRegionOffset = glm::ivec2(0)); + static Transform evalSubregionTexcoordTransform(const glm::ivec2& sourceSurface, const glm::ivec4& destViewport); + protected: SwapchainPointer _swapchain; diff --git a/libraries/gpu/src/gpu/PackedNormal.slh b/libraries/gpu/src/gpu/PackedNormal.slh new file mode 100644 index 0000000000..ecb383fc30 --- /dev/null +++ b/libraries/gpu/src/gpu/PackedNormal.slh @@ -0,0 +1,61 @@ + +<@if not PACKED_NORMAL_SLH@> +<@def PACKED_NORMAL_SLH@> + +vec2 signNotZero(vec2 v) { + return vec2((v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0); +} + +vec2 float32x3_to_oct(in vec3 v) { + vec2 p = v.xy * (1.0 / (abs(v.x) + abs(v.y) + abs(v.z))); + return ((v.z <= 0.0) ? ((1.0 - abs(p.yx)) * signNotZero(p)) : p); +} + + +vec3 oct_to_float32x3(in vec2 e) { + vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y)); + if (v.z < 0) { + v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy); + } + return normalize(v); +} + +vec3 snorm12x2_to_unorm8x3(vec2 f) { + vec2 u = vec2(round(clamp(f, -1.0, 1.0) * 2047.0 + 2047.0)); + float t = floor(u.y / 256.0); + + return floor(vec3( + u.x / 16.0, + fract(u.x / 16.0) * 256.0 + t, + u.y - t * 256.0 + )) / 255.0; +} + +vec2 unorm8x3_to_snorm12x2(vec3 u) { + u *= 255.0; + u.y *= (1.0 / 16.0); + vec2 s = vec2( u.x * 16.0 + floor(u.y), + fract(u.y) * (16.0 * 256.0) + u.z); + return clamp(s * (1.0 / 2047.0) - 1.0, vec2(-1.0), vec2(1.0)); +} + + +// Recommended function to pack/unpack vec3 normals to vec3 rgb with best efficiency +vec3 packNormal(in vec3 n) { + return snorm12x2_to_unorm8x3(float32x3_to_oct(n)); +} + +vec3 unpackNormal(in vec3 p) { + return oct_to_float32x3(unorm8x3_to_snorm12x2(p)); +} + +<@endif@> diff --git a/libraries/gpu/src/gpu/Query.cpp b/libraries/gpu/src/gpu/Query.cpp index 2e28dcd061..76c239b1e0 100644 --- a/libraries/gpu/src/gpu/Query.cpp +++ b/libraries/gpu/src/gpu/Query.cpp @@ -25,7 +25,7 @@ Query::~Query() } double Query::getElapsedTime() const { - return ((double) _queryResult) * 0.000001; + return ((double)_queryResult) / 1000000.0; } void Query::triggerReturnHandler(uint64_t queryResult) { diff --git a/libraries/gpu/src/gpu/Query.h b/libraries/gpu/src/gpu/Query.h index 8e4026fe0f..48b9d0a0d5 100644 --- a/libraries/gpu/src/gpu/Query.h +++ b/libraries/gpu/src/gpu/Query.h @@ -66,6 +66,9 @@ namespace gpu { int rangeIndex(int index) const { return (index % QUERY_QUEUE_SIZE); } }; + + using RangeTimerPointer = std::shared_ptr; + }; #endif diff --git a/libraries/gpu/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h index 21164955d6..10c83dfb0e 100644 --- a/libraries/gpu/src/gpu/Resource.h +++ b/libraries/gpu/src/gpu/Resource.h @@ -184,8 +184,34 @@ public: return append(sizeof(T) * t.size(), reinterpret_cast(&t[0])); } + bool getNextTransferBlock(Size& outOffset, Size& outSize, Size& currentPage) const { + Size pageCount = _pages.size(); + // Advance to the first dirty page + while (currentPage < pageCount && (0 == (Buffer::DIRTY & _pages[currentPage]))) { + ++currentPage; + } + + // If we got to the end, we're done + if (currentPage >= pageCount) { + return false; + } + + // Advance to the next clean page + outOffset = static_cast(currentPage * _pageSize); + while (currentPage < pageCount && (0 != (Buffer::DIRTY & _pages[currentPage]))) { + _pages[currentPage] &= ~Buffer::DIRTY; + ++currentPage; + } + outSize = static_cast((currentPage * _pageSize) - outOffset); + return true; + } + const GPUObjectPointer gpuObject {}; + // Access the sysmem object, limited to ourselves and GPUObject derived classes + const Sysmem& getSysmem() const { return _sysmem; } + // FIXME find a better access mechanism for clearing this + mutable uint8_t _flags; protected: void markDirty(Size offset, Size bytes); @@ -194,21 +220,18 @@ protected: markDirty(sizeof(T) * index, sizeof(T) * count); } - // Access the sysmem object, limited to ourselves and GPUObject derived classes - const Sysmem& getSysmem() const { return _sysmem; } Sysmem& editSysmem() { return _sysmem; } Byte* editData() { return editSysmem().editData(); } Size getRequiredPageCount() const; Size _end { 0 }; - mutable uint8_t _flags; mutable PageFlags _pages; const Size _pageSize; Sysmem _sysmem; // FIXME find a more generic way to do this. - friend class gl::GLBackend; + friend class gl::GLBuffer; friend class BufferView; }; diff --git a/libraries/gpu/src/gpu/Shader.cpp b/libraries/gpu/src/gpu/Shader.cpp index 74b7734618..a044e4845e 100755 --- a/libraries/gpu/src/gpu/Shader.cpp +++ b/libraries/gpu/src/gpu/Shader.cpp @@ -31,6 +31,13 @@ Shader::Shader(Type type, const Pointer& vertex, const Pointer& pixel): _shaders[PIXEL] = pixel; } +Shader::Shader(Type type, const Pointer& vertex, const Pointer& geometry, const Pointer& pixel) : +_type(type) { + _shaders.resize(3); + _shaders[VERTEX] = vertex; + _shaders[GEOMETRY] = geometry; + _shaders[PIXEL] = pixel; +} Shader::~Shader() { @@ -44,6 +51,10 @@ Shader::Pointer Shader::createPixel(const Source& source) { return Pointer(new Shader(PIXEL, source)); } +Shader::Pointer Shader::createGeometry(const Source& source) { + return Pointer(new Shader(GEOMETRY, source)); +} + Shader::Pointer Shader::createProgram(const Pointer& vertexShader, const Pointer& pixelShader) { if (vertexShader && vertexShader->getType() == VERTEX && pixelShader && pixelShader->getType() == PIXEL) { @@ -52,6 +63,15 @@ Shader::Pointer Shader::createProgram(const Pointer& vertexShader, const Pointer return Pointer(); } +Shader::Pointer Shader::createProgram(const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader) { + if (vertexShader && vertexShader->getType() == VERTEX && + geometryShader && geometryShader->getType() == GEOMETRY && + pixelShader && pixelShader->getType() == PIXEL) { + return Pointer(new Shader(PROGRAM, vertexShader, geometryShader, pixelShader)); + } + return Pointer(); +} + void Shader::defineSlots(const SlotSet& uniforms, const SlotSet& buffers, const SlotSet& textures, const SlotSet& samplers, const SlotSet& inputs, const SlotSet& outputs) { _uniforms = uniforms; _buffers = buffers; diff --git a/libraries/gpu/src/gpu/Shader.h b/libraries/gpu/src/gpu/Shader.h index 59c6401150..9072bf23a9 100755 --- a/libraries/gpu/src/gpu/Shader.h +++ b/libraries/gpu/src/gpu/Shader.h @@ -110,8 +110,10 @@ public: static Pointer createVertex(const Source& source); static Pointer createPixel(const Source& source); + static Pointer createGeometry(const Source& source); static Pointer createProgram(const Pointer& vertexShader, const Pointer& pixelShader); + static Pointer createProgram(const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader); ~Shader(); @@ -120,6 +122,9 @@ public: bool isProgram() const { return getType() > NUM_DOMAINS; } bool isDomain() const { return getType() < NUM_DOMAINS; } + void setCompilationHasFailed(bool compilationHasFailed) { _compilationHasFailed = compilationHasFailed; } + bool compilationHasFailed() const { return _compilationHasFailed; } + const Source& getSource() const { return _source; } const Shaders& getShaders() const { return _shaders; } @@ -160,6 +165,7 @@ public: protected: Shader(Type type, const Source& source); Shader(Type type, const Pointer& vertex, const Pointer& pixel); + Shader(Type type, const Pointer& vertex, const Pointer& geometry, const Pointer& pixel); Shader(const Shader& shader); // deep copy of the sysmem shader Shader& operator=(const Shader& shader); // deep copy of the sysmem texture @@ -180,6 +186,9 @@ protected: // The type of the shader, the master key Type _type; + + // Whether or not the shader compilation failed + bool _compilationHasFailed { false }; }; typedef Shader::Pointer ShaderPointer; diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 3c8d39a838..f9a8f3b26b 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -22,7 +22,7 @@ using namespace gpu; -static int TexturePointerMetaTypeId = qRegisterMetaType(); +int TexturePointerMetaTypeId = qRegisterMetaType(); std::atomic Texture::_textureCPUCount{ 0 }; std::atomic Texture::_textureCPUMemoryUsage{ 0 }; diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index 078b19d494..12a1cd10f1 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -18,6 +18,7 @@ struct TransformCamera { mat4 _projection; mat4 _projectionInverse; vec4 _viewport; + vec4 _stereoInfo; }; layout(std140) uniform transformCameraBuffer { @@ -27,6 +28,20 @@ layout(std140) uniform transformCameraBuffer { TransformCamera getTransformCamera() { return _camera; } + +vec3 getEyeWorldPos() { + return _camera._viewInverse[3].xyz; +} + + +bool cam_isStereo() { + return _camera._stereoInfo.x > 0.0; +} + +float cam_getStereoSide() { + return _camera._stereoInfo.y; +} + <@endfunc@> @@ -119,6 +134,15 @@ TransformObject getTransformObject() { } <@endfunc@> +<@func transformModelToWorldDir(cameraTransform, objectTransform, modelDir, worldDir)@> + { // transformModelToEyeDir + vec3 mr0 = <$objectTransform$>._modelInverse[0].xyz; + vec3 mr1 = <$objectTransform$>._modelInverse[1].xyz; + vec3 mr2 = <$objectTransform$>._modelInverse[2].xyz; + + <$worldDir$> = vec3(dot(mr0, <$modelDir$>), dot(mr1, <$modelDir$>), dot(mr2, <$modelDir$>)); + } +<@endfunc@> <@func transformModelToEyeDir(cameraTransform, objectTransform, modelDir, eyeDir)@> { // transformModelToEyeDir diff --git a/libraries/gpu/src/gpu/null/NullBackend.h b/libraries/gpu/src/gpu/null/NullBackend.h new file mode 100644 index 0000000000..097cee27e7 --- /dev/null +++ b/libraries/gpu/src/gpu/null/NullBackend.h @@ -0,0 +1,54 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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_gpu_Null_Backend_h +#define hifi_gpu_Null_Backend_h + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../Context.h" + +namespace gpu { namespace null { + +class Backend : public gpu::Backend { + using Parent = gpu::Backend; + // Context Backend static interface required + friend class gpu::Context; + static void init() {} + static gpu::Backend* createBackend() { return new Backend(); } + static bool makeProgram(Shader& shader, const Shader::BindingSet& slotBindings) { return true; } + +protected: + explicit Backend(bool syncCache) : Parent() { } + Backend() : Parent() { } +public: + ~Backend() { } + + void render(Batch& batch) final { } + + // This call synchronize the Full Backend cache with the current GLState + // THis is only intended to be used when mixing raw gl calls with the gpu api usage in order to sync + // the gpu::Backend state with the true gl state which has probably been messed up by these ugly naked gl calls + // Let's try to avoid to do that as much as possible! + void syncCache() final { } + + // This is the ugly "download the pixels to sysmem for taking a snapshot" + // Just avoid using it, it's ugly and will break performances + virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) final { } +}; + +} } + +#endif diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt index b81554511d..b0ea13843b 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME input-plugins) setup_hifi_library() -link_hifi_libraries(shared plugins controllers) +link_hifi_libraries(shared plugins ui-plugins controllers) GroupSources("src/input-plugins") diff --git a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp index 4d59adb602..acd7a20327 100644 --- a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp +++ b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp @@ -13,11 +13,13 @@ #include #include "KeyboardMouseDevice.h" +#include "TouchscreenDevice.h" // TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class InputPluginList getInputPlugins() { InputPlugin* PLUGIN_POOL[] = { new KeyboardMouseDevice(), + new TouchscreenDevice(), nullptr }; @@ -25,7 +27,6 @@ InputPluginList getInputPlugins() { for (int i = 0; PLUGIN_POOL[i]; ++i) { InputPlugin* plugin = PLUGIN_POOL[i]; if (plugin->isSupported()) { - plugin->init(); result.push_back(InputPluginPointer(plugin)); } } diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp old mode 100644 new mode 100755 index 4c0240eaf7..c1f764f5fa --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -19,12 +19,12 @@ #include const QString KeyboardMouseDevice::NAME = "Keyboard/Mouse"; +bool KeyboardMouseDevice::_enableTouch = true; -void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { - +void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { auto userInputMapper = DependencyManager::get(); userInputMapper->withLock([&, this]() { - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + _inputDevice->update(deltaTime, inputCalibrationData); }); // For touch event, we need to check that the last event is not too long ago @@ -40,7 +40,7 @@ void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputC } } -void KeyboardMouseDevice::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void KeyboardMouseDevice::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _axisStateMap.clear(); } @@ -50,9 +50,11 @@ void KeyboardMouseDevice::InputDevice::focusOutEvent() { void KeyboardMouseDevice::keyPressEvent(QKeyEvent* event) { auto input = _inputDevice->makeInput((Qt::Key) event->key()); - auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel()); - if (!result.second) { - // key pressed again ? without catching the release event ? + if (!(event->modifiers() & Qt::KeyboardModifier::ControlModifier)) { + auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel()); + if (result.second) { + // key pressed again ? without catching the release event ? + } } } @@ -80,7 +82,7 @@ void KeyboardMouseDevice::mouseReleaseEvent(QMouseEvent* event) { // if we pressed and released at the same location within a small time window, then create a "_CLICKED" // input for this button we might want to add some small tolerance to this so if you do a small drag it - // till counts as a clicked. + // still counts as a click. static const int CLICK_TIME = USECS_PER_MSEC * 500; // 500 ms to click if (!_mouseMoved && (usecTimestampNow() - _mousePressTime < CLICK_TIME)) { _inputDevice->_buttonPressedMap.insert(_inputDevice->makeInput((Qt::MouseButton) event->button(), true).getChannel()); @@ -99,7 +101,7 @@ void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event) { _inputDevice->_axisStateMap[MOUSE_AXIS_X_POS] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); _inputDevice->_axisStateMap[MOUSE_AXIS_X_NEG] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); - // Y mouse is inverted positive is pointing up the screen + // Y mouse is inverted positive is pointing up the screen _inputDevice->_axisStateMap[MOUSE_AXIS_Y_POS] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); _inputDevice->_axisStateMap[MOUSE_AXIS_Y_NEG] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); @@ -133,34 +135,40 @@ glm::vec2 evalAverageTouchPoints(const QList& points) { } void KeyboardMouseDevice::touchBeginEvent(const QTouchEvent* event) { - _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); - _lastTouch = evalAverageTouchPoints(event->touchPoints()); - _lastTouchTime = _clock.now(); + if (_enableTouch) { + _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); + _lastTouch = evalAverageTouchPoints(event->touchPoints()); + _lastTouchTime = _clock.now(); + } } void KeyboardMouseDevice::touchEndEvent(const QTouchEvent* event) { - _isTouching = false; - _lastTouch = evalAverageTouchPoints(event->touchPoints()); - _lastTouchTime = _clock.now(); + if (_enableTouch) { + _isTouching = false; + _lastTouch = evalAverageTouchPoints(event->touchPoints()); + _lastTouchTime = _clock.now(); + } } void KeyboardMouseDevice::touchUpdateEvent(const QTouchEvent* event) { - auto currentPos = evalAverageTouchPoints(event->touchPoints()); - _lastTouchTime = _clock.now(); - - if (!_isTouching) { - _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); - } else { - auto currentMove = currentPos - _lastTouch; - - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = (currentMove.x > 0 ? currentMove.x : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = (currentMove.x < 0 ? -currentMove.x : 0.0f); - // Y mouse is inverted positive is pointing up the screen - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = (currentMove.y < 0 ? -currentMove.y : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = (currentMove.y > 0 ? currentMove.y : 0.0f); - } + if (_enableTouch) { + auto currentPos = evalAverageTouchPoints(event->touchPoints()); + _lastTouchTime = _clock.now(); - _lastTouch = currentPos; + if (!_isTouching) { + _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); + } else { + auto currentMove = currentPos - _lastTouch; + + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = (currentMove.x > 0 ? currentMove.x : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = (currentMove.x < 0 ? -currentMove.x : 0.0f); + // Y mouse is inverted positive is pointing up the screen + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = (currentMove.y < 0 ? -currentMove.y : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = (currentMove.y > 0 ? currentMove.y : 0.0f); + } + + _lastTouch = currentPos; + } } controller::Input KeyboardMouseDevice::InputDevice::makeInput(Qt::Key code) const { @@ -248,4 +256,3 @@ QString KeyboardMouseDevice::InputDevice::getDefaultMappingConfig() const { static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/keyboardMouse.json"; return MAPPING_JSON; } - diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index 67d9ccb1b2..2fdecf0bba 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -65,12 +65,11 @@ public: }; // Plugin functions - virtual bool isSupported() const override { return true; } - virtual bool isJointController() const override { return false; } - virtual const QString& getName() const override { return NAME; } + bool isSupported() const override { return true; } + const QString& getName() const override { return NAME; } - virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void keyPressEvent(QKeyEvent* event); void keyReleaseEvent(QKeyEvent* event); @@ -85,6 +84,8 @@ public: void touchUpdateEvent(const QTouchEvent* event); void wheelEvent(QWheelEvent* event); + + static void enableTouch(bool enableTouch) { _enableTouch = enableTouch; } static const QString NAME; @@ -97,7 +98,7 @@ protected: // Device functions virtual controller::Input::NamedVector getAvailableInputs() const override; virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void focusOutEvent() override; // Let's make it easy for Qt because we assume we love Qt forever @@ -123,6 +124,8 @@ protected: bool _isTouching = false; std::chrono::high_resolution_clock _clock; std::chrono::high_resolution_clock::time_point _lastTouchTime; + + static bool _enableTouch; }; #endif // hifi_KeyboardMouseDevice_h diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp new file mode 100644 index 0000000000..64f02b5df3 --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp @@ -0,0 +1,132 @@ +// +// TouchscreenDevice.cpp +// input-plugins/src/input-plugins +// +// Created by Triplelexx on 01/31/16. +// Copyright 2016 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 "TouchscreenDevice.h" +#include "KeyboardMouseDevice.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +const QString TouchscreenDevice::NAME = "Touchscreen"; + +bool TouchscreenDevice::isSupported() const { + for (auto touchDevice : QTouchDevice::devices()) { + if (touchDevice->type() == QTouchDevice::TouchScreen) { + return true; + } + } + return false; +} + +void TouchscreenDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { + _inputDevice->update(deltaTime, inputCalibrationData); + }); + + float distanceScaleX, distanceScaleY; + if (_touchPointCount == 1) { + if (_firstTouchVec.x < _currentTouchVec.x) { + distanceScaleX = (_currentTouchVec.x - _firstTouchVec.x) / _screenDPIScale.x; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = distanceScaleX; + } else if (_firstTouchVec.x > _currentTouchVec.x) { + distanceScaleX = (_firstTouchVec.x - _currentTouchVec.x) / _screenDPIScale.x; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = distanceScaleX; + } + // Y axis is inverted, positive is pointing up the screen + if (_firstTouchVec.y > _currentTouchVec.y) { + distanceScaleY = (_firstTouchVec.y - _currentTouchVec.y) / _screenDPIScale.y; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = distanceScaleY; + } else if (_firstTouchVec.y < _currentTouchVec.y) { + distanceScaleY = (_currentTouchVec.y - _firstTouchVec.y) / _screenDPIScale.y; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = distanceScaleY; + } + } else if (_touchPointCount == 2) { + if (_pinchScale > _lastPinchScale && _pinchScale != 0) { + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_POS).getChannel()] = 1.0f; + } else if (_pinchScale != 0) { + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_NEG).getChannel()] = 1.0f; + } + _lastPinchScale = _pinchScale; + } +} + +void TouchscreenDevice::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + _axisStateMap.clear(); +} + +void TouchscreenDevice::InputDevice::focusOutEvent() { +} + +void TouchscreenDevice::touchBeginEvent(const QTouchEvent* event) { + const QTouchEvent::TouchPoint& point = event->touchPoints().at(0); + _firstTouchVec = glm::vec2(point.pos().x(), point.pos().y()); + KeyboardMouseDevice::enableTouch(false); + QScreen* eventScreen = event->window()->screen(); + if (_screenDPI != eventScreen->physicalDotsPerInch()) { + _screenDPIScale.x = (float)eventScreen->physicalDotsPerInchX(); + _screenDPIScale.y = (float)eventScreen->physicalDotsPerInchY(); + _screenDPI = eventScreen->physicalDotsPerInch(); + } +} + +void TouchscreenDevice::touchEndEvent(const QTouchEvent* event) { + _touchPointCount = 0; + KeyboardMouseDevice::enableTouch(true); +} + +void TouchscreenDevice::touchUpdateEvent(const QTouchEvent* event) { + const QTouchEvent::TouchPoint& point = event->touchPoints().at(0); + _currentTouchVec = glm::vec2(point.pos().x(), point.pos().y()); + _touchPointCount = event->touchPoints().count(); +} + +void TouchscreenDevice::touchGestureEvent(const QGestureEvent* event) { + if (QGesture* gesture = event->gesture(Qt::PinchGesture)) { + QPinchGesture* pinch = static_cast(gesture); + _pinchScale = pinch->totalScaleFactor(); + } +} + +controller::Input TouchscreenDevice::InputDevice::makeInput(TouchscreenDevice::TouchAxisChannel axis) const { + return controller::Input(_deviceID, axis, controller::ChannelType::AXIS); +} + +controller::Input TouchscreenDevice::InputDevice::makeInput(TouchscreenDevice::TouchGestureAxisChannel gesture) const { + return controller::Input(_deviceID, gesture, controller::ChannelType::AXIS); +} + +controller::Input::NamedVector TouchscreenDevice::InputDevice::getAvailableInputs() const { + using namespace controller; + static QVector availableInputs; + static std::once_flag once; + std::call_once(once, [&] { + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_X_POS), "DragRight")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_X_NEG), "DragLeft")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_POS), "DragUp")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_NEG), "DragDown")); + + availableInputs.append(Input::NamedPair(makeInput(TOUCH_GESTURE_PINCH_POS), "GesturePinchOut")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_GESTURE_PINCH_NEG), "GesturePinchIn")); + }); + return availableInputs; +} + +QString TouchscreenDevice::InputDevice::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/touchscreen.json"; + return MAPPING_JSON; +} diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h new file mode 100644 index 0000000000..f89f247ce8 --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h @@ -0,0 +1,84 @@ +// +// TouchscreenDevice.h +// input-plugins/src/input-plugins +// +// Created by Triplelexx on 1/31/16. +// Copyright 2016 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_TouchscreenDevice_h +#define hifi_TouchscreenDevice_h + +#include +#include "InputPlugin.h" +#include + +class QTouchEvent; +class QGestureEvent; + +class TouchscreenDevice : public InputPlugin { + Q_OBJECT +public: + + enum TouchAxisChannel { + TOUCH_AXIS_X_POS = 0, + TOUCH_AXIS_X_NEG, + TOUCH_AXIS_Y_POS, + TOUCH_AXIS_Y_NEG, + }; + + enum TouchGestureAxisChannel { + TOUCH_GESTURE_PINCH_POS = TOUCH_AXIS_Y_NEG + 1, + TOUCH_GESTURE_PINCH_NEG, + }; + + // Plugin functions + virtual bool isSupported() const override; + virtual const QString& getName() const override { return NAME; } + + virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + + void touchBeginEvent(const QTouchEvent* event); + void touchEndEvent(const QTouchEvent* event); + void touchUpdateEvent(const QTouchEvent* event); + void touchGestureEvent(const QGestureEvent* event); + + static const QString NAME; + +protected: + + class InputDevice : public controller::InputDevice { + public: + InputDevice() : controller::InputDevice("Touchscreen") {} + private: + // Device functions + virtual controller::Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + virtual void focusOutEvent() override; + + controller::Input makeInput(TouchAxisChannel axis) const; + controller::Input makeInput(TouchGestureAxisChannel gesture) const; + + friend class TouchscreenDevice; + }; + +public: + const std::shared_ptr& getInputDevice() const { return _inputDevice; } + +protected: + qreal _lastPinchScale; + qreal _pinchScale; + qreal _screenDPI; + glm::vec2 _screenDPIScale; + glm::vec2 _firstTouchVec; + glm::vec2 _currentTouchVec; + int _touchPointCount; + std::shared_ptr _inputDevice { std::make_shared() }; +}; + +#endif // hifi_TouchscreenDevice_h diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 8cd6d9b65e..4885186e47 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -70,7 +70,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { auto modelCache = DependencyManager::get(); GeometryExtra extra{ mapping, _textureBaseUrl }; - // Get the raw GeometryResource, not the wrapped NetworkGeometry + // Get the raw GeometryResource _geometryResource = modelCache->getResource(url, QUrl(), &extra).staticCast(); // Avoid caching nested resources - their references will be held by the parent _geometryResource->_isCacheable = false; @@ -90,8 +90,8 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { void GeometryMappingResource::onGeometryMappingLoaded(bool success) { if (success && _geometryResource) { - _geometry = _geometryResource->_geometry; - _shapes = _geometryResource->_shapes; + _fbxGeometry = _geometryResource->_fbxGeometry; + _meshParts = _geometryResource->_meshParts; _meshes = _geometryResource->_meshes; _materials = _geometryResource->_materials; @@ -200,31 +200,31 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { void GeometryDefinitionResource::setGeometryDefinition(FBXGeometry::Pointer fbxGeometry) { // Assume ownership of the geometry pointer - _geometry = fbxGeometry; + _fbxGeometry = fbxGeometry; // Copy materials QHash materialIDAtlas; - for (const FBXMaterial& material : _geometry->materials) { + for (const FBXMaterial& material : _fbxGeometry->materials) { materialIDAtlas[material.materialID] = _materials.size(); _materials.push_back(std::make_shared(material, _textureBaseUrl)); } - std::shared_ptr meshes = std::make_shared(); - std::shared_ptr shapes = std::make_shared(); + std::shared_ptr meshes = std::make_shared(); + std::shared_ptr parts = std::make_shared(); int meshID = 0; - for (const FBXMesh& mesh : _geometry->meshes) { + for (const FBXMesh& mesh : _fbxGeometry->meshes) { // Copy mesh pointers meshes->emplace_back(mesh._mesh); int partID = 0; for (const FBXMeshPart& part : mesh.parts) { - // Construct local shapes - shapes->push_back(std::make_shared(meshID, partID, (int)materialIDAtlas[part.materialID])); + // Construct local parts + parts->push_back(std::make_shared(meshID, partID, (int)materialIDAtlas[part.materialID])); partID++; } meshID++; } _meshes = meshes; - _shapes = shapes; + _meshParts = parts; finishedLoading(true); } @@ -250,17 +250,15 @@ QSharedPointer ModelCache::createResource(const QUrl& url, const QShar return QSharedPointer(resource, &Resource::deleter); } -std::shared_ptr ModelCache::getGeometry(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) { +GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) { GeometryExtra geometryExtra = { mapping, textureBaseUrl }; GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); } - return std::make_shared(resource); - } else { - return NetworkGeometry::Pointer(); } + return resource; } const QVariantMap Geometry::getTextures() const { @@ -278,9 +276,9 @@ const QVariantMap Geometry::getTextures() const { // FIXME: The materials should only be copied when modified, but the Model currently caches the original Geometry::Geometry(const Geometry& geometry) { - _geometry = geometry._geometry; + _fbxGeometry = geometry._fbxGeometry; _meshes = geometry._meshes; - _shapes = geometry._shapes; + _meshParts = geometry._meshParts; _materials.reserve(geometry._materials.size()); for (const auto& material : geometry._materials) { @@ -336,9 +334,9 @@ bool Geometry::areTexturesLoaded() const { return true; } -const std::shared_ptr Geometry::getShapeMaterial(int shapeID) const { - if ((shapeID >= 0) && (shapeID < (int)_shapes->size())) { - int materialID = _shapes->at(shapeID)->materialID; +const std::shared_ptr Geometry::getShapeMaterial(int partID) const { + if ((partID >= 0) && (partID < (int)_meshParts->size())) { + int materialID = _meshParts->at(partID)->materialID; if ((materialID >= 0) && (materialID < (int)_materials.size())) { return _materials[materialID]; } @@ -352,7 +350,7 @@ void GeometryResource::deleter() { } void GeometryResource::setTextures() { - for (const FBXMaterial& material : _geometry->materials) { + for (const FBXMaterial& material : _fbxGeometry->materials) { _materials.push_back(std::make_shared(material, _textureBaseUrl)); } } @@ -361,26 +359,40 @@ void GeometryResource::resetTextures() { _materials.clear(); } -NetworkGeometry::NetworkGeometry(const GeometryResource::Pointer& networkGeometry) : _resource(networkGeometry) { - connect(_resource.data(), &Resource::finished, this, &NetworkGeometry::resourceFinished); - connect(_resource.data(), &Resource::onRefresh, this, &NetworkGeometry::resourceRefreshed); +void GeometryResourceWatcher::startWatching() { + connect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); + connect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); if (_resource->isLoaded()) { resourceFinished(!_resource->getURL().isEmpty()); } } -void NetworkGeometry::resourceFinished(bool success) { - // FIXME: Model is not set up to handle a refresh - if (_instance) { - return; - } - if (success) { - _instance = std::make_shared(*_resource); - } - emit finished(success); +void GeometryResourceWatcher::stopWatching() { + disconnect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); + disconnect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); } -void NetworkGeometry::resourceRefreshed() { +void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) { + if (_resource) { + stopWatching(); + } + _resource = resource; + if (_resource) { + if (_resource->isLoaded()) { + _geometryRef = std::make_shared(*_resource); + } else { + startWatching(); + } + } +} + +void GeometryResourceWatcher::resourceFinished(bool success) { + if (success) { + _geometryRef = std::make_shared(*_resource); + } +} + +void GeometryResourceWatcher::resourceRefreshed() { // FIXME: Model is not set up to handle a refresh // _instance.reset(); } @@ -418,6 +430,8 @@ model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl, c auto map = std::make_shared(); map->setTextureSource(texture->_textureSource); + map->setTextureTransform(fbxTexture.transform); + return map; } @@ -427,6 +441,7 @@ model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, Textu auto map = std::make_shared(); map->setTextureSource(texture->_textureSource); + return map; } @@ -475,6 +490,7 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur if (!material.occlusionTexture.filename.isEmpty()) { auto map = fetchTextureMap(textureBaseUrl, material.occlusionTexture, NetworkTexture::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); + map->setTextureTransform(material.occlusionTexture.transform); setTextureMap(MapChannel::OCCLUSION_MAP, map); } @@ -483,6 +499,11 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur setTextureMap(MapChannel::EMISSIVE_MAP, map); } + if (!material.scatteringTexture.filename.isEmpty()) { + auto map = fetchTextureMap(textureBaseUrl, material.scatteringTexture, NetworkTexture::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP); + setTextureMap(MapChannel::SCATTERING_MAP, map); + } + if (!material.lightmapTexture.filename.isEmpty()) { auto map = fetchTextureMap(textureBaseUrl, material.lightmapTexture, NetworkTexture::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); _lightmapTransform = material.lightmapTexture.transform; @@ -503,6 +524,7 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { const auto& occlusionName = getTextureName(MapChannel::OCCLUSION_MAP); const auto& emissiveName = getTextureName(MapChannel::EMISSIVE_MAP); const auto& lightmapName = getTextureName(MapChannel::LIGHTMAP_MAP); + const auto& scatteringName = getTextureName(MapChannel::SCATTERING_MAP); if (!albedoName.isEmpty()) { auto url = textureMap.contains(albedoName) ? textureMap[albedoName].toUrl() : QUrl(); @@ -545,6 +567,12 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { setTextureMap(MapChannel::EMISSIVE_MAP, map); } + if (!scatteringName.isEmpty()) { + auto url = textureMap.contains(scatteringName) ? textureMap[scatteringName].toUrl() : QUrl(); + auto map = fetchTextureMap(url, NetworkTexture::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP); + setTextureMap(MapChannel::SCATTERING_MAP, map); + } + if (!lightmapName.isEmpty()) { auto url = textureMap.contains(lightmapName) ? textureMap[lightmapName].toUrl() : QUrl(); auto map = fetchTextureMap(url, NetworkTexture::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index f15e1106e2..aa3ea78db3 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -22,52 +22,30 @@ #include "TextureCache.h" // Alias instead of derive to avoid copying -using NetworkMesh = model::Mesh; class NetworkTexture; class NetworkMaterial; -class NetworkShape; -class NetworkGeometry; +class MeshPart; class GeometryMappingResource; -/// Stores cached model geometries. -class ModelCache : public ResourceCache, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: - /// Loads a model geometry from the specified URL. - std::shared_ptr getGeometry(const QUrl& url, - const QVariantHash& mapping = QVariantHash(), const QUrl& textureBaseUrl = QUrl()); - -protected: - friend class GeometryMappingResource; - - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra); - -private: - ModelCache(); - virtual ~ModelCache() = default; -}; - class Geometry { public: using Pointer = std::shared_ptr; + using WeakPointer = std::weak_ptr; Geometry() = default; Geometry(const Geometry& geometry); // Immutable over lifetime - using NetworkMeshes = std::vector>; - using NetworkShapes = std::vector>; + using GeometryMeshes = std::vector>; + using GeometryMeshParts = std::vector>; // Mutable, but must retain structure of vector using NetworkMaterials = std::vector>; - const FBXGeometry& getGeometry() const { return *_geometry; } - const NetworkMeshes& getMeshes() const { return *_meshes; } + const FBXGeometry& getFBXGeometry() const { return *_fbxGeometry; } + const GeometryMeshes& getMeshes() const { return *_meshes; } const std::shared_ptr getShapeMaterial(int shapeID) const; const QVariantMap getTextures() const; @@ -79,9 +57,9 @@ protected: friend class GeometryMappingResource; // Shared across all geometries, constant throughout lifetime - std::shared_ptr _geometry; - std::shared_ptr _meshes; - std::shared_ptr _shapes; + std::shared_ptr _fbxGeometry; + std::shared_ptr _meshes; + std::shared_ptr _meshParts; // Copied to each geometry, mutable throughout lifetime via setTextures NetworkMaterials _materials; @@ -108,7 +86,7 @@ protected: // Geometries may not hold onto textures while cached - that is for the texture cache // Instead, these methods clear and reset textures from the geometry when caching/loading - bool shouldSetTextures() const { return _geometry && _materials.empty(); } + bool shouldSetTextures() const { return _fbxGeometry && _materials.empty(); } void setTextures(); void resetTextures(); @@ -118,22 +96,21 @@ protected: bool _isCacheable { true }; }; -class NetworkGeometry : public QObject { +class GeometryResourceWatcher : public QObject { Q_OBJECT public: - using Pointer = std::shared_ptr; + using Pointer = std::shared_ptr; - NetworkGeometry() = delete; - NetworkGeometry(const GeometryResource::Pointer& networkGeometry); + GeometryResourceWatcher() = delete; + GeometryResourceWatcher(Geometry::Pointer& geometryPtr) : _geometryRef(geometryPtr) {} - const QUrl& getURL() { return _resource->getURL(); } + void setResource(GeometryResource::Pointer resource); - /// Returns the geometry, if it is loaded (must be checked!) - const Geometry::Pointer& getGeometry() { return _instance; } + QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); } -signals: - /// Emitted when the NetworkGeometry loads (or fails to) - void finished(bool success); +private: + void startWatching(); + void stopWatching(); private slots: void resourceFinished(bool success); @@ -141,7 +118,27 @@ private slots: private: GeometryResource::Pointer _resource; - Geometry::Pointer _instance { nullptr }; + Geometry::Pointer& _geometryRef; +}; + +/// Stores cached model geometries. +class ModelCache : public ResourceCache, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + GeometryResource::Pointer getGeometryResource(const QUrl& url, + const QVariantHash& mapping = QVariantHash(), const QUrl& textureBaseUrl = QUrl()); + +protected: + friend class GeometryMappingResource; + + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, + const void* extra); + +private: + ModelCache(); + virtual ~ModelCache() = default; }; class NetworkMaterial : public model::Material { @@ -185,9 +182,9 @@ private: bool _isOriginal { true }; }; -class NetworkShape { +class MeshPart { public: - NetworkShape(int mesh, int part, int material) : meshID { mesh }, partID { part }, materialID { material } {} + MeshPart(int mesh, int part, int material) : meshID { mesh }, partID { part }, materialID { material } {} int meshID { -1 }; int partID { -1 }; int materialID { -1 }; diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index f3c6a86b49..0108a3dd6c 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -51,6 +51,7 @@ public: EMISSIVE_TEXTURE, CUBE_TEXTURE, OCCLUSION_TEXTURE, + SCATTERING_TEXTURE = OCCLUSION_TEXTURE, LIGHTMAP_TEXTURE, CUSTOM_TEXTURE }; diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index 7cc4691d63..5e9d3d30c0 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -98,10 +98,18 @@ float getLightShowContour(Light l) { return l._control.w; } +// Light is the light source its self, d is the light's distance calculated as length(unnormalized light vector). float evalLightAttenuation(Light l, float d) { float radius = getLightRadius(l); float denom = d / radius + 1.0; - float attenuation = min(1.0, 1.0 / (denom * denom)); + float attenuation = 1.0 / (denom * denom); + + float cutoff = getLightCutoffRadius(l); + + // "Fade" the edges of light sources to make things look a bit more attractive. + // Note: this tends to look a bit odd at lower exponents. + attenuation *= min(1, max(0, -(d - cutoff))); + return attenuation; } @@ -118,28 +126,48 @@ float getLightAmbientMapNumMips(Light l) { } -<@if GPU_FEATURE_PROFILE == GPU_CORE @> uniform lightBuffer { Light light; }; Light getLight() { return light; } -<@else@> -uniform vec4 lightBuffer[7]; -Light getLight() { - Light light; - light._position = lightBuffer[0]; - light._direction = lightBuffer[1]; - light._color = lightBuffer[2]; - light._attenuation = lightBuffer[3]; - light._spot = lightBuffer[4]; - light._shadow = lightBuffer[5]; - light._control = lightBuffer[6]; - return light; + + + +bool clipFragToLightVolumePoint(Light light, vec3 fragPos, out vec4 fragLightVecLen2) { + fragLightVecLen2.xyz = getLightPosition(light) - fragPos.xyz; + fragLightVecLen2.w = dot(fragLightVecLen2.xyz, fragLightVecLen2.xyz); + + // Kill if too far from the light center + if (fragLightVecLen2.w > getLightCutoffSquareRadius(light)) { + return false; + } + return true; +} + +bool clipFragToLightVolumeSpot(Light light, vec3 fragPos, out vec4 fragLightVecLen2, out vec4 fragLightDirLen, out float cosSpotAngle) { + fragLightVecLen2.xyz = getLightPosition(light) - fragPos.xyz; + fragLightVecLen2.w = dot(fragLightVecLen2.xyz, fragLightVecLen2.xyz); + + // Kill if too far from the light center + if (fragLightVecLen2.w > getLightCutoffSquareRadius(light)) { + return false; + } + + // Allright we re valid in the volume + fragLightDirLen.w = length(fragLightVecLen2.xyz); + fragLightDirLen.xyz = fragLightVecLen2.xyz / fragLightDirLen.w; + + // Kill if not in the spot light (ah ah !) + cosSpotAngle = max(-dot(fragLightDirLen.xyz, getLightDirection(light)), 0.0); + if (cosSpotAngle < getLightSpotAngleCos(light)) { + return false; + } + + return true; } -<@endif@> diff --git a/libraries/model/src/model/Material.cpp b/libraries/model/src/model/Material.cpp index 53478be536..6e7968a571 100755 --- a/libraries/model/src/model/Material.cpp +++ b/libraries/model/src/model/Material.cpp @@ -104,6 +104,13 @@ void Material::setMetallic(float metallic) { _schemaBuffer.edit()._metallic = metallic; } +void Material::setScattering(float scattering) { + scattering = glm::clamp(scattering, 0.0f, 1.0f); + _key.setMetallic(scattering > 0.0f); + _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); + _schemaBuffer.edit()._scattering = scattering; +} + void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) { if (textureMap) { _key.setMapChannel(channel, (true)); @@ -122,6 +129,10 @@ void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textur _texMapArrayBuffer.edit()._texcoordTransforms[0] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); } + if (channel == MaterialKey::OCCLUSION_MAP) { + _texMapArrayBuffer.edit()._texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); + } + if (channel == MaterialKey::LIGHTMAP_MAP) { // update the texcoord1 with lightmap _texMapArrayBuffer.edit()._texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); diff --git a/libraries/model/src/model/Material.h b/libraries/model/src/model/Material.h index 8dd9dd7960..304ef2e93b 100755 --- a/libraries/model/src/model/Material.h +++ b/libraries/model/src/model/Material.h @@ -35,6 +35,7 @@ public: OPACITY_VAL_BIT, OPACITY_MASK_MAP_BIT, // Opacity Map and Opacity MASK map are mutually exclusive OPACITY_TRANSLUCENT_MAP_BIT, + SCATTERING_VAL_BIT, // THe map bits must be in the same sequence as the enum names for the map channels EMISSIVE_MAP_BIT, @@ -44,6 +45,7 @@ public: NORMAL_MAP_BIT, OCCLUSION_MAP_BIT, LIGHTMAP_MAP_BIT, + SCATTERING_MAP_BIT, NUM_FLAGS, }; @@ -57,6 +59,7 @@ public: NORMAL_MAP, OCCLUSION_MAP, LIGHTMAP_MAP, + SCATTERING_MAP, NUM_MAP_CHANNELS, }; @@ -83,6 +86,8 @@ public: Builder& withTranslucentFactor() { _flags.set(OPACITY_VAL_BIT); return (*this); } + Builder& withScattering() { _flags.set(SCATTERING_VAL_BIT); return (*this); } + Builder& withEmissiveMap() { _flags.set(EMISSIVE_MAP_BIT); return (*this); } Builder& withAlbedoMap() { _flags.set(ALBEDO_MAP_BIT); return (*this); } Builder& withMetallicMap() { _flags.set(METALLIC_MAP_BIT); return (*this); } @@ -94,6 +99,7 @@ public: Builder& withNormalMap() { _flags.set(NORMAL_MAP_BIT); return (*this); } Builder& withOcclusionMap() { _flags.set(OCCLUSION_MAP_BIT); return (*this); } Builder& withLightmapMap() { _flags.set(LIGHTMAP_MAP_BIT); return (*this); } + Builder& withScatteringMap() { _flags.set(SCATTERING_MAP_BIT); return (*this); } // Convenient standard keys that we will keep on using all over the place static MaterialKey opaqueAlbedo() { return Builder().withAlbedo().build(); } @@ -135,7 +141,7 @@ public: void setOpacityMaskMap(bool value) { _flags.set(OPACITY_MASK_MAP_BIT, value); } bool isOpacityMaskMap() const { return _flags[OPACITY_MASK_MAP_BIT]; } - + void setNormalMap(bool value) { _flags.set(NORMAL_MAP_BIT, value); } bool isNormalMap() const { return _flags[NORMAL_MAP_BIT]; } @@ -145,6 +151,12 @@ public: void setLightmapMap(bool value) { _flags.set(LIGHTMAP_MAP_BIT, value); } bool isLightmapMap() const { return _flags[LIGHTMAP_MAP_BIT]; } + void setScattering(bool value) { _flags.set(SCATTERING_VAL_BIT, value); } + bool isScattering() const { return _flags[SCATTERING_VAL_BIT]; } + + void setScatteringMap(bool value) { _flags.set(SCATTERING_MAP_BIT, value); } + bool isScatteringMap() const { return _flags[SCATTERING_MAP_BIT]; } + void setMapChannel(MapChannel channel, bool value) { _flags.set(EMISSIVE_MAP_BIT + channel, value); } bool isMapChannel(MapChannel channel) const { return _flags[EMISSIVE_MAP_BIT + channel]; } @@ -218,6 +230,13 @@ public: Builder& withoutLightmapMap() { _value.reset(MaterialKey::LIGHTMAP_MAP_BIT); _mask.set(MaterialKey::LIGHTMAP_MAP_BIT); return (*this); } Builder& withLightmapMap() { _value.set(MaterialKey::LIGHTMAP_MAP_BIT); _mask.set(MaterialKey::LIGHTMAP_MAP_BIT); return (*this); } + Builder& withoutScattering() { _value.reset(MaterialKey::SCATTERING_VAL_BIT); _mask.set(MaterialKey::SCATTERING_VAL_BIT); return (*this); } + Builder& withScattering() { _value.set(MaterialKey::SCATTERING_VAL_BIT); _mask.set(MaterialKey::SCATTERING_VAL_BIT); return (*this); } + + Builder& withoutScatteringMap() { _value.reset(MaterialKey::SCATTERING_MAP_BIT); _mask.set(MaterialKey::SCATTERING_MAP_BIT); return (*this); } + Builder& withScatteringMap() { _value.set(MaterialKey::SCATTERING_MAP_BIT); _mask.set(MaterialKey::SCATTERING_MAP_BIT); return (*this); } + + // Convenient standard keys that we will keep on using all over the place static MaterialFilter opaqueAlbedo() { return Builder().withAlbedo().withoutTranslucentFactor().build(); } }; @@ -275,6 +294,8 @@ public: void setRoughness(float roughness); float getRoughness() const { return _schemaBuffer.get()._roughness; } + void setScattering(float scattering); + float getScattering() const { return _schemaBuffer.get()._scattering; } // Schema to access the attribute values of the material class Schema { @@ -288,7 +309,9 @@ public: glm::vec3 _fresnel{ 0.03f }; // Fresnel value for a default non metallic float _metallic{ 0.0f }; // Not Metallic - glm::vec3 _spare{ 0.0f }; + float _scattering{ 0.0f }; // Scattering info + + glm::vec2 _spare{ 0.0f }; uint32_t _key{ 0 }; // a copy of the materialKey diff --git a/libraries/model/src/model/Material.slh b/libraries/model/src/model/Material.slh index 4a6139c664..2faf2d8569 100644 --- a/libraries/model/src/model/Material.slh +++ b/libraries/model/src/model/Material.slh @@ -15,7 +15,7 @@ struct Material { vec4 _emissiveOpacity; vec4 _albedoRoughness; vec4 _fresnelMetallic; - vec4 _spareKey; + vec4 _scatteringSpare2Key; }; uniform materialBuffer { @@ -37,7 +37,9 @@ float getMaterialMetallic(Material m) { return m._fresnelMetallic.a; } float getMaterialShininess(Material m) { return 1.0 - getMaterialRoughness(m); } -int getMaterialKey(Material m) { return floatBitsToInt(m._spareKey.w); } +float getMaterialScattering(Material m) { return m._scatteringSpare2Key.x; } + +int getMaterialKey(Material m) { return floatBitsToInt(m._scatteringSpare2Key.w); } const int EMISSIVE_VAL_BIT = 0x00000001; const int UNLIT_VAL_BIT = 0x00000002; @@ -47,14 +49,17 @@ const int GLOSSY_VAL_BIT = 0x00000010; const int OPACITY_VAL_BIT = 0x00000020; const int OPACITY_MASK_MAP_BIT = 0x00000040; const int OPACITY_TRANSLUCENT_MAP_BIT = 0x00000080; +const int SCATTERING_VAL_BIT = 0x00000100; -const int EMISSIVE_MAP_BIT = 0x00000100; -const int ALBEDO_MAP_BIT = 0x00000200; -const int METALLIC_MAP_BIT = 0x00000400; -const int ROUGHNESS_MAP_BIT = 0x00000800; -const int NORMAL_MAP_BIT = 0x00001000; -const int OCCLUSION_MAP_BIT = 0x00002000; -const int LIGHTMAP_MAP_BIT = 0x00004000; + +const int EMISSIVE_MAP_BIT = 0x00000200; +const int ALBEDO_MAP_BIT = 0x00000400; +const int METALLIC_MAP_BIT = 0x00000800; +const int ROUGHNESS_MAP_BIT = 0x00001000; +const int NORMAL_MAP_BIT = 0x00002000; +const int OCCLUSION_MAP_BIT = 0x00004000; +const int LIGHTMAP_MAP_BIT = 0x00008000; +const int SCATTERING_MAP_BIT = 0x00010000; <@endif@> diff --git a/libraries/model/src/model/skybox.slv b/libraries/model/src/model/skybox.slv index 810afb1033..5df1aa0a4a 100755 --- a/libraries/model/src/model/skybox.slv +++ b/libraries/model/src/model/skybox.slv @@ -36,4 +36,4 @@ void main(void) { // Position is supposed to come in clip space gl_Position = vec4(inPosition.xy, 0.0, 1.0); -} \ No newline at end of file +} diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 8c23844f4e..ed325b8d69 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AccountManager.h" + #include #include @@ -26,32 +28,23 @@ #include +#include "NetworkLogging.h" #include "NodeList.h" #include "udt/PacketHeaders.h" #include "RSAKeypairGenerator.h" #include "SharedUtil.h" +#include "UserActivityLogger.h" -#include "AccountManager.h" -#include "NetworkLogging.h" const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; -AccountManager& AccountManager::getInstance(bool forceReset) { - static std::unique_ptr sharedInstance(new AccountManager()); - - if (forceReset) { - sharedInstance.reset(new AccountManager()); - } - - return *sharedInstance; -} - Q_DECLARE_METATYPE(OAuthAccessToken) Q_DECLARE_METATYPE(DataServerAccountInfo) Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) Q_DECLARE_METATYPE(JSONCallbackParameters) const QString ACCOUNTS_GROUP = "accounts"; +static const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit(); JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod, QObject* errorCallbackReceiver, const QString& errorCallbackMethod, @@ -79,7 +72,8 @@ QJsonObject AccountManager::dataObjectFromResponse(QNetworkReply &requestReply) } } -AccountManager::AccountManager() : +AccountManager::AccountManager(UserAgentGetter userAgentGetter) : + _userAgentGetter(userAgentGetter), _authURL(), _pendingCallbackMap() { @@ -180,6 +174,7 @@ void AccountManager::setAuthURL(const QUrl& authURL) { << "from previous settings file"; } } + settings.endGroup(); if (_accountInfo.getAccessToken().token.isEmpty()) { qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded."; @@ -222,8 +217,12 @@ void AccountManager::sendRequest(const QString& path, QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest; - networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - + + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); + + networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER, + uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); + QUrl requestURL = _authURL; if (path.startsWith("/")) { @@ -319,6 +318,9 @@ void AccountManager::processReply() { QNetworkReply* requestReply = reinterpret_cast(sender()); if (requestReply->error() == QNetworkReply::NoError) { + if (requestReply->hasRawHeader(METAVERSE_SESSION_ID_HEADER)) { + _sessionID = requestReply->rawHeader(METAVERSE_SESSION_ID_HEADER); + } passSuccessToCallback(requestReply); } else { passErrorToCallback(requestReply); @@ -468,12 +470,17 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) persistAccountToFile(); } +void AccountManager::setTemporaryDomain(const QUuid& domainID, const QString& key) { + _accountInfo.setTemporaryDomain(domainID, key); + persistAccountToFile(); +} + void AccountManager::requestAccessToken(const QString& login, const QString& password) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request; - request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); QUrl grantURL = _authURL; grantURL.setPath("/oauth/token"); @@ -543,7 +550,7 @@ void AccountManager::requestProfile() { profileURL.setPath("/api/v1/user/profile"); QNetworkRequest profileRequest(profileURL); - profileRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + profileRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue()); QNetworkReply* profileReply = networkAccessManager.get(profileRequest); @@ -644,22 +651,33 @@ void AccountManager::processGeneratedKeypair() { const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key"; QString uploadPath; - if (keypairGenerator->getDomainID().isNull()) { + const auto& domainID = keypairGenerator->getDomainID(); + if (domainID.isNull()) { uploadPath = USER_PUBLIC_KEY_UPDATE_PATH; } else { - uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(keypairGenerator->getDomainID())); + uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(domainID)); } // setup a multipart upload to send up the public key QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - QHttpPart keyPart; - keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - keyPart.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); - keyPart.setBody(keypairGenerator->getPublicKey()); + QHttpPart publicKeyPart; + publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - requestMultiPart->append(keyPart); + publicKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); + publicKeyPart.setBody(keypairGenerator->getPublicKey()); + requestMultiPart->append(publicKeyPart); + + if (!domainID.isNull()) { + const auto& key = getTemporaryDomainKey(domainID); + QHttpPart apiKeyPart; + publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"api_key\"")); + apiKeyPart.setBody(key.toUtf8()); + requestMultiPart->append(apiKeyPart); + } // setup callback parameters so we know once the keypair upload has succeeded or failed JSONCallbackParameters callbackParameters; diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 108b49f678..c12f663d3e 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -20,12 +20,15 @@ #include "NetworkAccessManager.h" #include "DataServerAccountInfo.h" +#include "SharedUtil.h" + +#include class JSONCallbackParameters { public: - JSONCallbackParameters(QObject* jsonCallbackReceiver = NULL, const QString& jsonCallbackMethod = QString(), - QObject* errorCallbackReceiver = NULL, const QString& errorCallbackMethod = QString(), - QObject* updateReceiver = NULL, const QString& updateSlot = QString()); + JSONCallbackParameters(QObject* jsonCallbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(), + QObject* errorCallbackReceiver = nullptr, const QString& errorCallbackMethod = QString(), + QObject* updateReceiver = nullptr, const QString& updateSlot = QString()); bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; } @@ -49,10 +52,14 @@ Q_DECLARE_METATYPE(AccountManagerAuth::Type); const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; -class AccountManager : public QObject { +using UserAgentGetter = std::function; + +const auto DEFAULT_USER_AGENT_GETTER = []() -> QString { return HIGH_FIDELITY_USER_AGENT; }; + +class AccountManager : public QObject, public Dependency { Q_OBJECT public: - static AccountManager& getInstance(bool forceReset = false); + AccountManager(UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER); Q_INVOKABLE void sendRequest(const QString& path, AccountManagerAuth::Type authType, @@ -79,6 +86,12 @@ public: static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply); + QUuid getSessionID() const { return _sessionID; } + void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; } + + void setTemporaryDomain(const QUuid& domainID, const QString& key); + const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); } + public slots: void requestAccessToken(const QString& login, const QString& password); @@ -109,7 +122,6 @@ private slots: void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid()); private: - AccountManager(); AccountManager(AccountManager const& other) = delete; void operator=(AccountManager const& other) = delete; @@ -119,6 +131,8 @@ private: void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); + UserAgentGetter _userAgentGetter; + QUrl _authURL; QMap _pendingCallbackMap; @@ -128,6 +142,8 @@ private: bool _isWaitingForKeypairResponse { false }; QByteArray _pendingPrivateKey; + + QUuid _sessionID { QUuid::createUuid() }; }; #endif // hifi_AccountManager_h diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 27647d2694..ae6aad3c4f 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -23,6 +23,8 @@ #include "AddressManager.h" #include "NodeList.h" #include "NetworkLogging.h" +#include "UserActivityLogger.h" +#include "udt/PacketHeaders.h" const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; @@ -36,6 +38,10 @@ AddressManager::AddressManager() : } +QString AddressManager::protocolVersion() { + return protocolVersionsSignatureBase64(); +} + bool AddressManager::isConnected() { return DependencyManager::get()->getDomainHandler().isConnected(); } @@ -130,6 +136,10 @@ const JSONCallbackParameters& AddressManager::apiCallbackParameters() { } bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { + static QString URL_TYPE_USER = "user"; + static QString URL_TYPE_DOMAIN_ID = "domain_id"; + static QString URL_TYPE_PLACE = "place"; + static QString URL_TYPE_NETWORK_ADDRESS = "network_address"; if (lookupUrl.scheme() == HIFI_URL_SCHEME) { qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); @@ -144,12 +154,25 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // 4. domain network address (IP or dns resolvable hostname) // use our regex'ed helpers to figure out what we're supposed to do with this - if (!handleUsername(lookupUrl.authority())) { + if (handleUsername(lookupUrl.authority())) { + // handled a username for lookup + + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString()); + + // in case we're failing to connect to where we thought this user was + // store their username as previous lookup so we can refresh their location via API + _previousLookup = lookupUrl; + } else { // we're assuming this is either a network address or global place name // check if it is a network address first bool hostChanged; if (handleNetworkAddress(lookupUrl.host() - + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { + + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { + + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_NETWORK_ADDRESS, lookupUrl.toString()); + + // a network address lookup clears the previous lookup since we don't expect to re-attempt it + _previousLookup.clear(); // If the host changed then we have already saved to history if (hostChanged) { @@ -165,10 +188,20 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // we may have a path that defines a relative viewpoint - if so we should jump to that now handlePath(path, trigger); } else if (handleDomainID(lookupUrl.host())){ + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_DOMAIN_ID, lookupUrl.toString()); + + // store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info + _previousLookup = lookupUrl; + // no place name - this is probably a domain ID // try to look up the domain ID on the metaverse API attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger); } else { + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_PLACE, lookupUrl.toString()); + + // store this place name as the previous lookup in case we fail to connect and want to refresh API info + _previousLookup = lookupUrl; + // wasn't an address - lookup the place name // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path(), trigger); @@ -180,16 +213,20 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } else if (lookupUrl.toString().startsWith('/')) { qCDebug(networking) << "Going to relative path" << lookupUrl.path(); + // a path lookup clears the previous lookup since we don't expect to re-attempt it + _previousLookup.clear(); + // if this is a relative path then handle it as a relative viewpoint handlePath(lookupUrl.path(), trigger, true); emit lookupResultsFinished(); + return true; } return false; } -void AddressManager::handleLookupString(const QString& lookupString) { +void AddressManager::handleLookupString(const QString& lookupString, bool fromSuggestions) { if (!lookupString.isEmpty()) { // make this a valid hifi URL and handle it off to handleUrl QString sanitizedString = lookupString.trimmed(); @@ -204,7 +241,7 @@ void AddressManager::handleLookupString(const QString& lookupString) { lookupURL = QUrl(lookupString); } - handleUrl(lookupURL); + handleUrl(lookupURL, fromSuggestions ? Suggestions : UserInput); } } @@ -276,7 +313,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const qCDebug(networking) << "Possible domain change required to connect to" << domainHostname << "on" << domainPort; - emit possibleDomainChangeRequired(domainHostname, domainPort); + emit possibleDomainChangeRequired(domainHostname, domainPort, domainID); } else { QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString(); @@ -295,21 +332,30 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const // set our current root place name to the name that came back const QString PLACE_NAME_KEY = "name"; QString placeName = rootMap[PLACE_NAME_KEY].toString(); + if (!placeName.isEmpty()) { if (setHost(placeName, trigger)) { trigger = LookupTrigger::Internal; } + + _placeName = placeName; } else { if (setHost(domainIDString, trigger)) { trigger = LookupTrigger::Internal; } + + // this isn't a place, so clear the place name + _placeName.clear(); } // check if we had a path to override the path returned QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString(); if (!overridePath.isEmpty()) { - handlePath(overridePath, trigger); + // make sure we don't re-handle an overriden path if this was a refresh of info from API + if (trigger != LookupTrigger::AttemptedRefresh) { + handlePath(overridePath, trigger); + } } else { // take the path that came back const QString PLACE_PATH_KEY = "path"; @@ -355,8 +401,12 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) { qCDebug(networking) << "AddressManager API error -" << errorReply.error() << "-" << errorReply.errorString(); if (errorReply.error() == QNetworkReply::ContentNotFoundError) { + // if this is a lookup that has no result, don't keep re-trying it + _previousLookup.clear(); + emit lookupResultIsNotFound(); } + emit lookupResultsFinished(); } @@ -374,7 +424,7 @@ void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const Q // remember how this lookup was triggered for history storage handling later requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(trigger)); - AccountManager::getInstance().sendRequest(GET_PLACE.arg(placeName), + DependencyManager::get()->sendRequest(GET_PLACE.arg(placeName), AccountManagerAuth::None, QNetworkAccessManager::GetOperation, apiCallbackParameters(), @@ -397,7 +447,7 @@ void AddressManager::attemptDomainIDLookup(const QString& lookupString, const QS // remember how this lookup was triggered for history storage handling later requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(trigger)); - AccountManager::getInstance().sendRequest(GET_DOMAIN_ID.arg(domainID), + DependencyManager::get()->sendRequest(GET_DOMAIN_ID.arg(domainID), AccountManagerAuth::None, QNetworkAccessManager::GetOperation, apiCallbackParameters(), @@ -519,10 +569,10 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should if (viewpointString[positionRegex.matchedLength() - 1] == QChar('/') && orientationRegex.indexIn(viewpointString, positionRegex.matchedLength() - 1) != -1) { - glm::quat newOrientation = glm::normalize(glm::quat(orientationRegex.cap(4).toFloat(), - orientationRegex.cap(1).toFloat(), - orientationRegex.cap(2).toFloat(), - orientationRegex.cap(3).toFloat())); + newOrientation = glm::normalize(glm::quat(orientationRegex.cap(4).toFloat(), + orientationRegex.cap(1).toFloat(), + orientationRegex.cap(2).toFloat(), + orientationRegex.cap(3).toFloat())); if (!isNaN(newOrientation.x) && !isNaN(newOrientation.y) && !isNaN(newOrientation.z) && !isNaN(newOrientation.w)) { @@ -577,17 +627,18 @@ bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 return false; } - bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger) { bool hostChanged = setHost(hostname, trigger, port); + // clear any current place information _rootPlaceID = QUuid(); + _placeName.clear(); qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port; DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); - emit possibleDomainChangeRequired(hostname, port); + emit possibleDomainChangeRequired(hostname, port, QUuid()); return hostChanged; } @@ -600,13 +651,20 @@ void AddressManager::goToUser(const QString& username) { requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(LookupTrigger::UserInput)); // this is a username - pull the captured name and lookup that user's location - AccountManager::getInstance().sendRequest(GET_USER_LOCATION.arg(formattedUsername), + DependencyManager::get()->sendRequest(GET_USER_LOCATION.arg(formattedUsername), AccountManagerAuth::Optional, QNetworkAccessManager::GetOperation, apiCallbackParameters(), QByteArray(), nullptr, requestParams); } +void AddressManager::refreshPreviousLookup() { + // if we have a non-empty previous lookup, fire it again now (but don't re-store it in the history) + if (!_previousLookup.isEmpty()) { + handleUrl(_previousLookup, LookupTrigger::AttemptedRefresh); + } +} + void AddressManager::copyAddress() { QApplication::clipboard()->setText(currentAddress().toString()); } @@ -618,7 +676,10 @@ void AddressManager::copyPath() { void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { // if we're cold starting and this is called for the first address (from settings) we don't do anything - if (trigger != LookupTrigger::StartupFromSettings && trigger != LookupTrigger::DomainPathResponse) { + if (trigger != LookupTrigger::StartupFromSettings + && trigger != LookupTrigger::DomainPathResponse + && trigger != LookupTrigger::AttemptedRefresh) { + if (trigger == LookupTrigger::Back) { // we're about to push to the forward stack // if it's currently empty emit our signal to say that going forward is now possible diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index dd0dbd9f38..2e9f177137 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -39,6 +39,7 @@ class AddressManager : public QObject, public Dependency { Q_PROPERTY(QString hostname READ getHost) Q_PROPERTY(QString pathname READ currentPath) public: + Q_INVOKABLE QString protocolVersion(); using PositionGetter = std::function; using OrientationGetter = std::function; @@ -48,7 +49,9 @@ public: Forward, StartupFromSettings, DomainPathResponse, - Internal + Internal, + AttemptedRefresh, + Suggestions }; bool isConnected(); @@ -58,6 +61,7 @@ public: const QString currentPath(bool withOrientation = true) const; const QUuid& getRootPlaceID() const { return _rootPlaceID; } + const QString& getPlaceName() const { return _placeName; } const QString& getHost() const { return _host; } @@ -75,7 +79,7 @@ public: std::function localSandboxNotRunningDoThat); public slots: - void handleLookupString(const QString& lookupString); + void handleLookupString(const QString& lookupString, bool fromSuggestions = false); // we currently expect this to be called from NodeList once handleLookupString has been called with a path bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) @@ -88,6 +92,8 @@ public slots: void goToUser(const QString& username); + void refreshPreviousLookup(); + void storeCurrentAddress(); void copyAddress(); @@ -98,7 +104,7 @@ signals: void lookupResultIsOffline(); void lookupResultIsNotFound(); - void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort); + void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort, const QUuid& domainID); void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID); void locationChangeRequired(const glm::vec3& newPosition, @@ -141,6 +147,7 @@ private: QString _host; quint16 _port; + QString _placeName; QUuid _rootPlaceID; PositionGetter _positionGetter; OrientationGetter _orientationGetter; @@ -150,6 +157,8 @@ private: quint64 _lastBackPush = 0; QString _newHostLookupPath; + + QUrl _previousLookup; }; #endif // hifi_AddressManager_h diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 211bfdccfa..6c6f3eb90c 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -25,6 +25,8 @@ #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif +const QString DataServerAccountInfo::EMPTY_KEY = QString(); + DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() { _accessToken = otherInfo._accessToken; _username = otherInfo._username; @@ -33,6 +35,8 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI _walletID = otherInfo._walletID; _privateKey = otherInfo._privateKey; _domainID = otherInfo._domainID; + _temporaryDomainID = otherInfo._temporaryDomainID; + _temporaryDomainApiKey = otherInfo._temporaryDomainApiKey; } DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) { @@ -51,6 +55,8 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { swap(_walletID, otherInfo._walletID); swap(_privateKey, otherInfo._privateKey); swap(_domainID, otherInfo._domainID); + swap(_temporaryDomainID, otherInfo._temporaryDomainID); + swap(_temporaryDomainApiKey, otherInfo._temporaryDomainApiKey); } void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) { @@ -145,13 +151,14 @@ QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) { QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey - << info._walletID << info._privateKey << info._domainID; - + << info._walletID << info._privateKey << info._domainID + << info._temporaryDomainID << info._temporaryDomainApiKey; return out; } QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey - >> info._walletID >> info._privateKey >> info._domainID; + >> info._walletID >> info._privateKey >> info._domainID + >> info._temporaryDomainID >> info._temporaryDomainApiKey; return in; } diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index 6ee726efde..8cb416cf34 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -22,6 +22,7 @@ const float SATOSHIS_PER_CREDIT = 100000000.0f; class DataServerAccountInfo : public QObject { Q_OBJECT + const static QString EMPTY_KEY; public: DataServerAccountInfo() {}; DataServerAccountInfo(const DataServerAccountInfo& otherInfo); @@ -52,6 +53,9 @@ public: void setDomainID(const QUuid& domainID) { _domainID = domainID; } const QUuid& getDomainID() const { return _domainID; } + void setTemporaryDomain(const QUuid& domainID, const QString& key) { _temporaryDomainID = domainID; _temporaryDomainApiKey = key; } + const QString& getTemporaryDomainKey(const QUuid& domainID) { return domainID == _temporaryDomainID ? _temporaryDomainApiKey : EMPTY_KEY; } + bool hasProfile() const; void setProfileInfoFromJSON(const QJsonObject& jsonObject); @@ -67,7 +71,9 @@ private: QString _xmppPassword; QString _discourseApiKey; QUuid _walletID; - QUuid _domainID; // if this holds account info for a domain, this holds the ID of that domain + QUuid _domainID; + QUuid _temporaryDomainID; + QString _temporaryDomainApiKey; QByteArray _privateKey; }; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index b2eb0cd680..739c0f8f4a 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -14,6 +14,7 @@ #include #include +#include "AddressManager.h" #include "Assignment.h" #include "HifiSockAddr.h" #include "NodeList.h" @@ -28,17 +29,10 @@ DomainHandler::DomainHandler(QObject* parent) : QObject(parent), - _uuid(), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), - _assignmentUUID(), - _connectionToken(), - _iceDomainID(), - _iceClientID(), - _iceServerSockAddr(), _icePeer(this), - _isConnected(false), - _settingsObject(), - _settingsTimer(this) + _settingsTimer(this), + _apiRefreshTimer(this) { _sockAddr.setObjectName("DomainServer"); @@ -49,6 +43,16 @@ DomainHandler::DomainHandler(QObject* parent) : static const int DOMAIN_SETTINGS_TIMEOUT_MS = 5000; _settingsTimer.setInterval(DOMAIN_SETTINGS_TIMEOUT_MS); connect(&_settingsTimer, &QTimer::timeout, this, &DomainHandler::settingsReceiveFail); + + // setup the API refresh timer for auto connection information refresh from API when failing to connect + const int API_REFRESH_TIMEOUT_MSEC = 2500; + _apiRefreshTimer.setInterval(API_REFRESH_TIMEOUT_MSEC); + + auto addressManager = DependencyManager::get(); + connect(&_apiRefreshTimer, &QTimer::timeout, addressManager.data(), &AddressManager::refreshPreviousLookup); + + // stop the refresh timer if we connect to a domain + connect(this, &DomainHandler::connectedToDomain, &_apiRefreshTimer, &QTimer::stop); } void DomainHandler::disconnect() { @@ -97,6 +101,9 @@ void DomainHandler::softReset() { // cancel the failure timeout for any pending requests for settings QMetaObject::invokeMethod(&_settingsTimer, "stop"); + + // restart the API refresh timer in case we fail to connect and need to refresh information + QMetaObject::invokeMethod(&_apiRefreshTimer, "start"); } void DomainHandler::hardReset() { @@ -105,14 +112,15 @@ void DomainHandler::hardReset() { softReset(); qCDebug(networking) << "Hard reset in NodeList DomainHandler."; - _iceDomainID = QUuid(); + _pendingDomainID = QUuid(); _iceServerSockAddr = HifiSockAddr(); _hostname = QString(); _sockAddr.clear(); - _hasCheckedForAccessToken = false; _domainConnectionRefusals.clear(); + _hasCheckedForAccessToken = false; + // clear any pending path we may have wanted to ask the previous DS about _pendingPath.clear(); } @@ -140,7 +148,9 @@ void DomainHandler::setUUID(const QUuid& uuid) { } } -void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) { +void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const QUuid& domainID) { + + _pendingDomainID = domainID; if (hostname != _hostname || _sockAddr.getPort() != port) { // re-set the domain info so that auth information is reloaded @@ -172,14 +182,15 @@ void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) { } void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) { - if (id != _uuid) { + + if (_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) { // re-set the domain info to connect to new domain hardReset(); // refresh our ICE client UUID to something new _iceClientID = QUuid::createUuid(); - _iceDomainID = id; + _pendingDomainID = id; HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr; replaceableSockAddr->~HifiSockAddr(); @@ -253,6 +264,7 @@ void DomainHandler::setIsConnected(bool isConnected) { // we've connected to new domain - time to ask it for global settings requestDomainSettings(); + } else { emit disconnectedFromDomain(); } @@ -303,6 +315,9 @@ void DomainHandler::processICEPingReplyPacket(QSharedPointer me qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr; if (getIP().isNull()) { + // we're hearing back from this domain-server, no need to refresh API information + _apiRefreshTimer.stop(); + // for now we're unsafely assuming this came back from the domain if (senderSockAddr == _icePeer.getLocalSocket()) { qCDebug(networking) << "Connecting to domain using local socket"; @@ -331,17 +346,20 @@ void DomainHandler::processDTLSRequirementPacket(QSharedPointer 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 + // bail on processing this packet if our ice peer already has sockets return; } + // start or restart the API refresh timer now that we have new information + _apiRefreshTimer.start(); + QDataStream iceResponseStream(message->getMessage()); iceResponseStream >> _icePeer; DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation); - if (_icePeer.getUUID() != _iceDomainID) { + if (_icePeer.getUUID() != _pendingDomainID) { qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection."; _icePeer.reset(); } else { @@ -355,34 +373,61 @@ void DomainHandler::processICEResponsePacket(QSharedPointer mes } } +bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) { + switch (reasonCode) { + case ConnectionRefusedReason::LoginError: + case ConnectionRefusedReason::NotAuthorized: + return true; + + default: + case ConnectionRefusedReason::Unknown: + case ConnectionRefusedReason::ProtocolMismatch: + case ConnectionRefusedReason::TooManyUsers: + return false; + } + return false; +} + void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer message) { + // we're hearing from this domain-server, don't need to refresh API info + _apiRefreshTimer.stop(); + // Read deny reason from packet + uint8_t reasonCodeWire; + + message->readPrimitive(&reasonCodeWire); + ConnectionRefusedReason reasonCode = static_cast(reasonCodeWire); quint16 reasonSize; message->readPrimitive(&reasonSize); - QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize)); + auto reasonText = message->readWithoutCopy(reasonSize); + QString reasonMessage = QString::fromUtf8(reasonText); // 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 - qCWarning(networking) << "The domain-server denied a connection request: " << reason; - qCWarning(networking) << "Make sure you are logged in."; + qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage; - if (!_domainConnectionRefusals.contains(reason)) { - _domainConnectionRefusals.append(reason); - emit domainConnectionRefused(reason); + if (!_domainConnectionRefusals.contains(reasonMessage)) { + _domainConnectionRefusals.insert(reasonMessage); + emit domainConnectionRefused(reasonMessage, (int)reasonCode); } - auto& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); - if (!_hasCheckedForAccessToken) { - accountManager.checkAndSignalForAccessToken(); - _hasCheckedForAccessToken = true; - } + // Some connection refusal reasons imply that a login is required. If so, suggest a new login + if (reasonSuggestsLogin(reasonCode)) { + qCWarning(networking) << "Make sure you are logged in."; - static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3; + if (!_hasCheckedForAccessToken) { + accountManager->checkAndSignalForAccessToken(); + _hasCheckedForAccessToken = true; + } - // force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts - if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) { - accountManager.generateNewUserKeypair(); - _connectionDenialsSinceKeypairRegen = 0; + static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3; + + // force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts + if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) { + accountManager->generateNewUserKeypair(); + _connectionDenialsSinceKeypairRegen = 0; + } } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 03141d8fef..50639a4817 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -58,8 +58,8 @@ public: const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } - - const QUuid& getICEDomainID() const { return _iceDomainID; } + + const QUuid& getPendingDomainID() const { return _pendingDomainID; } const QUuid& getICEClientID() const { return _iceClientID; } @@ -75,7 +75,6 @@ public: bool hasSettings() const { return !_settingsObject.isEmpty(); } void requestDomainSettings(); const QJsonObject& getSettingsObject() const { return _settingsObject; } - void setPendingPath(const QString& pendingPath) { _pendingPath = pendingPath; } const QString& getPendingPath() { return _pendingPath; } @@ -84,8 +83,17 @@ public: bool isSocketKnown() const { return !_sockAddr.getAddress().isNull(); } void softReset(); + + enum class ConnectionRefusedReason : uint8_t { + Unknown, + ProtocolMismatch, + LoginError, + NotAuthorized, + TooManyUsers + }; + public slots: - void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT); + void setSocketAndID(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT, const QUuid& id = QUuid()); void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id); void processSettingsPacketList(QSharedPointer packetList); @@ -115,9 +123,10 @@ signals: void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); - void domainConnectionRefused(QString reason); + void domainConnectionRefused(QString reasonMessage, int reason); private: + bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode); void sendDisconnectPacket(); void hardReset(); @@ -126,18 +135,20 @@ private: HifiSockAddr _sockAddr; QUuid _assignmentUUID; QUuid _connectionToken; - QUuid _iceDomainID; + QUuid _pendingDomainID; // ID of domain being connected to, via ICE or direct connection QUuid _iceClientID; HifiSockAddr _iceServerSockAddr; NetworkPeer _icePeer; - bool _isConnected; + bool _isConnected { false }; QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; - QStringList _domainConnectionRefusals; + QSet _domainConnectionRefusals; bool _hasCheckedForAccessToken { false }; int _connectionDenialsSinceKeypairRegen { 0 }; + + QTimer _apiRefreshTimer; }; #endif // hifi_DomainHandler_h diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index bc4c1a1599..bde0aca7b7 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -16,7 +16,7 @@ #include "HifiSockAddr.h" #include "NetworkLogging.h" -static int hifiSockAddrMetaTypeId = qRegisterMetaType(); +int hifiSockAddrMetaTypeId = qRegisterMetaType(); HifiSockAddr::HifiSockAddr() : _address(), diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 2c10d0627e..d83046bc1b 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -34,7 +34,7 @@ #include "NetworkLogging.h" #include "udt/Packet.h" -const char SOLO_NODE_TYPES[2] = { +const std::set SOLO_NODE_TYPES = { NodeType::AvatarMixer, NodeType::AudioMixer }; @@ -52,7 +52,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short _numCollectedPackets(0), _numCollectedBytes(0), _packetStatTimer(), - _thisNodeCanRez(true) + _permissions(NodePermissions()) { static bool firstCall = true; if (firstCall) { @@ -130,17 +130,22 @@ void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) { } } -void LimitedNodeList::setIsAllowedEditor(bool isAllowedEditor) { - if (_isAllowedEditor != isAllowedEditor) { - _isAllowedEditor = isAllowedEditor; - emit isAllowedEditorChanged(isAllowedEditor); - } -} +void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) { + NodePermissions originalPermissions = _permissions; -void LimitedNodeList::setThisNodeCanRez(bool canRez) { - if (_thisNodeCanRez != canRez) { - _thisNodeCanRez = canRez; - emit canRezChanged(canRez); + _permissions = newPermissions; + + if (originalPermissions.canAdjustLocks != newPermissions.canAdjustLocks) { + emit isAllowedEditorChanged(_permissions.canAdjustLocks); + } + if (originalPermissions.canRezPermanentEntities != newPermissions.canRezPermanentEntities) { + emit canRezChanged(_permissions.canRezPermanentEntities); + } + if (originalPermissions.canRezTemporaryEntities != newPermissions.canRezTemporaryEntities) { + emit canRezTmpChanged(_permissions.canRezTemporaryEntities); + } + if (originalPermissions.canWriteToAssetServer != newPermissions.canWriteToAssetServer) { + emit canWriteAssetsChanged(_permissions.canWriteToAssetServer); } } @@ -176,9 +181,10 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { bool hasBeenOutput = false; QString senderString; + const HifiSockAddr& senderSockAddr = packet.getSenderSockAddr(); + QUuid sourceID; if (NON_SOURCED_PACKETS.contains(headerType)) { - const HifiSockAddr& senderSockAddr = packet.getSenderSockAddr(); hasBeenOutput = versionDebugSuppressMap.contains(senderSockAddr, headerType); if (!hasBeenOutput) { @@ -186,7 +192,7 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { senderString = QString("%1:%2").arg(senderSockAddr.getAddress().toString()).arg(senderSockAddr.getPort()); } } else { - QUuid sourceID = NLPacket::sourceIDInHeader(packet); + sourceID = NLPacket::sourceIDInHeader(packet); hasBeenOutput = sourcedVersionDebugSuppressMap.contains(sourceID, headerType); @@ -201,7 +207,7 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { << senderString << "sent" << qPrintable(QString::number(headerVersion)) << "but" << qPrintable(QString::number(versionForPacketType(headerType))) << "expected."; - emit packetVersionMismatch(headerType); + emit packetVersionMismatch(headerType, senderSockAddr, sourceID); } return false; @@ -419,12 +425,8 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& } int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer message, SharedNodePointer sendingNode) { - QMutexLocker locker(&sendingNode->getMutex()); - NodeData* linkedData = sendingNode->getLinkedData(); - if (!linkedData && linkedDataCreateCallback) { - linkedDataCreateCallback(sendingNode.data()); - } + NodeData* linkedData = getOrCreateLinkedData(sendingNode); if (linkedData) { QMutexLocker linkedDataLocker(&linkedData->getMutex()); @@ -434,6 +436,17 @@ int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointergetMutex()); + + NodeData* linkedData = node->getLinkedData(); + if (!linkedData && linkedDataCreateCallback) { + linkedDataCreateCallback(node.data()); + } + + return node->getLinkedData(); +} + SharedNodePointer LimitedNodeList::nodeWithUUID(const QUuid& nodeUUID) { QReadLocker readLocker(&_nodeMutex); @@ -514,23 +527,23 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) { SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool isAllowedEditor, bool canRez, + const NodePermissions& permissions, const QUuid& connectionSecret) { + QReadLocker readLocker(&_nodeMutex); NodeHash::const_iterator it = _nodeHash.find(uuid); if (it != _nodeHash.end()) { SharedNodePointer& matchingNode = it->second; - + matchingNode->setPublicSocket(publicSocket); matchingNode->setLocalSocket(localSocket); - matchingNode->setIsAllowedEditor(isAllowedEditor); - matchingNode->setCanRez(canRez); + matchingNode->setPermissions(permissions); matchingNode->setConnectionSecret(connectionSecret); return matchingNode; } else { // we didn't have this node, so add them - Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, isAllowedEditor, canRez, connectionSecret, this); + Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, permissions, connectionSecret, this); if (nodeType == NodeType::AudioMixer) { LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer); @@ -538,7 +551,33 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t SharedNodePointer newNodePointer(newNode, &QObject::deleteLater); + // if this is a solo node type, we assume that the DS has replaced its assignment and we should kill the previous node + if (SOLO_NODE_TYPES.count(newNode->getType())) { + // while we still have the read lock, see if there is a previous solo node we'll need to remove + auto previousSoloIt = std::find_if(_nodeHash.cbegin(), _nodeHash.cend(), [newNode](const UUIDNodePair& nodePair){ + return nodePair.second->getType() == newNode->getType(); + }); + + if (previousSoloIt != _nodeHash.cend()) { + // we have a previous solo node, switch to a write lock so we can remove it + readLocker.unlock(); + + QWriteLocker writeLocker(&_nodeMutex); + + auto oldSoloNode = previousSoloIt->second; + + _nodeHash.unsafe_erase(previousSoloIt); + handleNodeKill(oldSoloNode); + + // convert the current lock back to a read lock for insertion of new node + writeLocker.unlock(); + readLocker.relock(); + } + } + + // insert the new node and release our read lock _nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer)); + readLocker.unlock(); qCDebug(networking) << "Added" << *newNode; diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 0cbe9668b3..d599fbcc37 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #ifndef _WIN32 @@ -46,7 +47,7 @@ const quint64 NODE_SILENCE_THRESHOLD_MSECS = 5 * 1000; -extern const char SOLO_NODE_TYPES[2]; +extern const std::set SOLO_NODE_TYPES; const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost"; @@ -104,12 +105,12 @@ public: const QUuid& getSessionUUID() const { return _sessionUUID; } void setSessionUUID(const QUuid& sessionUUID); - bool isAllowedEditor() const { return _isAllowedEditor; } - void setIsAllowedEditor(bool isAllowedEditor); + void setPermissions(const NodePermissions& newPermissions); + bool isAllowedEditor() const { return _permissions.canAdjustLocks; } + bool getThisNodeCanRez() const { return _permissions.canRezPermanentEntities; } + bool getThisNodeCanRezTmp() const { return _permissions.canRezTemporaryEntities; } + bool getThisNodeCanWriteAssets() const { return _permissions.canWriteToAssetServer; } - bool getThisNodeCanRez() const { return _thisNodeCanRez; } - void setThisNodeCanRez(bool canRez); - quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } QUdpSocket& getDTLSSocket(); @@ -131,13 +132,13 @@ public: std::function linkedDataCreateCallback; - size_t size() const { return _nodeHash.size(); } + size_t size() const { QReadLocker readLock(&_nodeMutex); return _nodeHash.size(); } SharedNodePointer nodeWithUUID(const QUuid& nodeUUID); SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool isAllowedEditor = false, bool canRez = false, + const NodePermissions& permissions = DEFAULT_AGENT_PERMISSIONS, const QUuid& connectionSecret = QUuid()); bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } @@ -149,6 +150,7 @@ public: void processKillNode(ReceivedMessage& message); int updateNodeWithDataFromPacket(QSharedPointer packet, SharedNodePointer matchingNode); + NodeData* getOrCreateLinkedData(SharedNodePointer node); unsigned int broadcastToNodes(std::unique_ptr packet, const NodeSet& destinationNodeTypes); SharedNodePointer soloNodeOfType(NodeType_t nodeType); @@ -221,6 +223,10 @@ public: void setConnectionMaxBandwidth(int maxBandwidth) { _nodeSocket.setConnectionMaxBandwidth(maxBandwidth); } + void setPacketFilterOperator(udt::PacketFilterOperator filterOperator) { _nodeSocket.setPacketFilterOperator(filterOperator); } + bool packetVersionMatch(const udt::Packet& packet); + bool isPacketVerified(const udt::Packet& packet); + public slots: void reset(); void eraseAllNodes(); @@ -236,7 +242,9 @@ public slots: signals: void dataSent(quint8 channelType, int bytes); - void packetVersionMismatch(PacketType type); + + // QUuid might be zero for non-sourced packet types. + void packetVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID); void nodeAdded(SharedNodePointer); @@ -248,6 +256,8 @@ signals: void isAllowedEditorChanged(bool isAllowedEditor); void canRezChanged(bool canRez); + void canRezTmpChanged(bool canRezTmp); + void canWriteAssetsChanged(bool canWriteAssets); protected slots: void connectedForLocalSocketTest(); @@ -267,8 +277,6 @@ protected: void setLocalSocket(const HifiSockAddr& sockAddr); - bool isPacketVerified(const udt::Packet& packet); - bool packetVersionMatch(const udt::Packet& packet); bool packetSourceAndHashMatch(const udt::Packet& packet); void processSTUNResponse(std::unique_ptr packet); @@ -281,7 +289,7 @@ protected: QUuid _sessionUUID; NodeHash _nodeHash; - QReadWriteLock _nodeMutex; + mutable QReadWriteLock _nodeMutex; udt::Socket _nodeSocket; QUdpSocket* _dtlsSocket; HifiSockAddr _localSockAddr; @@ -296,8 +304,7 @@ protected: int _numCollectedBytes; QElapsedTimer _packetStatTimer; - bool _isAllowedEditor { false }; - bool _thisNodeCanRez; + NodePermissions _permissions; QPointer _initialSTUNTimer; diff --git a/libraries/networking/src/NLPacket.cpp b/libraries/networking/src/NLPacket.cpp index 575a2c7a9c..a11dd69753 100644 --- a/libraries/networking/src/NLPacket.cpp +++ b/libraries/networking/src/NLPacket.cpp @@ -24,8 +24,8 @@ int NLPacket::maxPayloadSize(PacketType type, bool isPartOfMessage) { return Packet::maxPayloadSize(isPartOfMessage) - NLPacket::localHeaderSize(type); } -std::unique_ptr NLPacket::create(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage) { - auto packet = std::unique_ptr(new NLPacket(type, size, isReliable, isPartOfMessage)); +std::unique_ptr NLPacket::create(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage, PacketVersion version) { + auto packet = std::unique_ptr(new NLPacket(type, size, isReliable, isPartOfMessage, version)); packet->open(QIODevice::ReadWrite); @@ -61,13 +61,13 @@ std::unique_ptr NLPacket::createCopy(const NLPacket& other) { return std::unique_ptr(new NLPacket(other)); } -NLPacket::NLPacket(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage) : +NLPacket::NLPacket(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage, PacketVersion version) : Packet((size == -1) ? -1 : NLPacket::localHeaderSize(type) + size, isReliable, isPartOfMessage), _type(type), - _version(versionForPacketType(type)) + _version((version == 0) ? versionForPacketType(type) : version) { adjustPayloadStartAndCapacity(NLPacket::localHeaderSize(_type)); - + writeTypeAndVersion(); } @@ -184,6 +184,11 @@ void NLPacket::setType(PacketType type) { writeTypeAndVersion(); } +void NLPacket::setVersion(PacketVersion version) { + _version = version; + writeTypeAndVersion(); +} + void NLPacket::readType() { _type = NLPacket::typeInHeader(*this); } diff --git a/libraries/networking/src/NLPacket.h b/libraries/networking/src/NLPacket.h index 4527094322..33de262dfb 100644 --- a/libraries/networking/src/NLPacket.h +++ b/libraries/networking/src/NLPacket.h @@ -38,7 +38,7 @@ public: sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; static std::unique_ptr create(PacketType type, qint64 size = -1, - bool isReliable = false, bool isPartOfMessage = false); + bool isReliable = false, bool isPartOfMessage = false, PacketVersion version = 0); static std::unique_ptr fromReceivedPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); @@ -65,6 +65,7 @@ public: void setType(PacketType type); PacketVersion getVersion() const { return _version; } + void setVersion(PacketVersion version); const QUuid& getSourceID() const { return _sourceID; } @@ -73,7 +74,7 @@ public: protected: - NLPacket(PacketType type, qint64 size = -1, bool forceReliable = false, bool isPartOfMessage = false); + NLPacket(PacketType type, qint64 size = -1, bool forceReliable = false, bool isPartOfMessage = false, PacketVersion version = 0); NLPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); NLPacket(const NLPacket& other); diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index e4fe292223..406498b025 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -12,19 +12,22 @@ #include #include -#include - -#include "Node.h" -#include "SharedUtil.h" - #include #include +#include + +#include "NetworkLogging.h" +#include "NodePermissions.h" +#include "SharedUtil.h" + +#include "Node.h" + const QString UNKNOWN_NodeType_t_NAME = "Unknown"; -static int NodePtrMetaTypeId = qRegisterMetaType("Node*"); -static int sharedPtrNodeMetaTypeId = qRegisterMetaType>("QSharedPointer"); -static int sharedNodePtrMetaTypeId = qRegisterMetaType("SharedNodePointer"); +int NodePtrMetaTypeId = qRegisterMetaType("Node*"); +int sharedPtrNodeMetaTypeId = qRegisterMetaType>("QSharedPointer"); +int sharedNodePtrMetaTypeId = qRegisterMetaType("SharedNodePointer"); namespace NodeType { QHash TypeNameHash; @@ -47,7 +50,7 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) { } Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, - const HifiSockAddr& localSocket, bool isAllowedEditor, bool canRez, const QUuid& connectionSecret, + const HifiSockAddr& localSocket, const NodePermissions& permissions, const QUuid& connectionSecret, QObject* parent) : NetworkPeer(uuid, publicSocket, localSocket, parent), _type(type), @@ -57,8 +60,7 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, _clockSkewUsec(0), _mutex(), _clockSkewMovingPercentile(30, 0.8f), // moving 80th percentile of 30 samples - _isAllowedEditor(isAllowedEditor), - _canRez(canRez) + _permissions(permissions) { // Update socket's object name setType(_type); @@ -78,15 +80,33 @@ void Node::updateClockSkewUsec(qint64 clockSkewSample) { _clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile(); } +void Node::parseIgnoreRequestMessage(QSharedPointer message) { + while (message->getBytesLeftToRead()) { + // parse out the UUID being ignored from the packet + QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + addIgnoredNode(ignoredUUID); + } +} + +void Node::addIgnoredNode(const QUuid& otherNodeID) { + if (!otherNodeID.isNull() && otherNodeID != _uuid) { + qCDebug(networking) << "Adding" << uuidStringWithoutCurlyBraces(otherNodeID) << "to ignore set for" + << uuidStringWithoutCurlyBraces(_uuid); + + // add the session UUID to the set of ignored ones for this listening node + _ignoredNodeIDSet.insert(otherNodeID); + } else { + qCWarning(networking) << "Node::addIgnoredNode called with null ID or ID of ignoring node."; + } +} QDataStream& operator<<(QDataStream& out, const Node& node) { out << node._type; out << node._uuid; out << node._publicSocket; out << node._localSocket; - out << node._isAllowedEditor; - out << node._canRez; - + out << node._permissions; return out; } @@ -95,9 +115,7 @@ QDataStream& operator>>(QDataStream& in, Node& node) { in >> node._uuid; in >> node._publicSocket; in >> node._localSocket; - in >> node._isAllowedEditor; - in >> node._canRez; - + in >> node._permissions; return in; } diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 3927672319..469a0c9755 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -21,19 +21,24 @@ #include #include +#include + +#include + #include "HifiSockAddr.h" #include "NetworkPeer.h" #include "NodeData.h" #include "NodeType.h" #include "SimpleMovingAverage.h" #include "MovingPercentile.h" +#include "NodePermissions.h" class Node : public NetworkPeer { Q_OBJECT public: Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool isAllowedEditor, bool canRez, const QUuid& connectionSecret = QUuid(), + const NodePermissions& permissions, const QUuid& connectionSecret = QUuid(), QObject* parent = 0); bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; } @@ -58,11 +63,16 @@ public: void updateClockSkewUsec(qint64 clockSkewSample); QMutex& getMutex() { return _mutex; } - void setIsAllowedEditor(bool isAllowedEditor) { _isAllowedEditor = isAllowedEditor; } - bool isAllowedEditor() { return _isAllowedEditor; } + void setPermissions(const NodePermissions& newPermissions) { _permissions = newPermissions; } + NodePermissions getPermissions() const { return _permissions; } + bool isAllowedEditor() const { return _permissions.canAdjustLocks; } + bool getCanRez() const { return _permissions.canRezPermanentEntities; } + bool getCanRezTmp() const { return _permissions.canRezTemporaryEntities; } + bool getCanWriteToAssetServer() const { return _permissions.canWriteToAssetServer; } - void setCanRez(bool canRez) { _canRez = canRez; } - bool getCanRez() { return _canRez; } + void parseIgnoreRequestMessage(QSharedPointer message); + void addIgnoredNode(const QUuid& otherNodeID); + bool isIgnoringNodeWithID(const QUuid& nodeID) const { return _ignoredNodeIDSet.find(nodeID) != _ignoredNodeIDSet.cend(); } friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node); @@ -81,8 +91,8 @@ private: qint64 _clockSkewUsec; QMutex _mutex; MovingPercentile _clockSkewMovingPercentile; - bool _isAllowedEditor; - bool _canRez; + NodePermissions _permissions; + tbb::concurrent_unordered_set _ignoredNodeIDSet; }; Q_DECLARE_METATYPE(Node*) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 25dfe884db..a73537aad0 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -50,7 +50,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // handle domain change signals from AddressManager connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired, - &_domainHandler, &DomainHandler::setHostnameAndPort); + &_domainHandler, &DomainHandler::setSocketAndID); connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID, &_domainHandler, &DomainHandler::setIceServerHostnameAndID); @@ -80,19 +80,22 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // send a ping punch immediately connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer); - auto &accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); // assume that we may need to send a new DS check in anytime a new keypair is generated - connect(&accountManager, &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn); + connect(accountManager.data(), &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn); // clear out NodeList when login is finished - connect(&accountManager, &AccountManager::loginComplete , this, &NodeList::reset); + connect(accountManager.data(), &AccountManager::loginComplete , this, &NodeList::reset); // clear our NodeList when logout is requested - connect(&accountManager, &AccountManager::logoutComplete , this, &NodeList::reset); + connect(accountManager.data(), &AccountManager::logoutComplete , this, &NodeList::reset); // anytime we get a new node we will want to attempt to punch to it connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); + + // anytime we get a new node we may need to re-send our set of ignored node IDs to it + connect(this, &LimitedNodeList::nodeActivated, this, &NodeList::maybeSendIgnoreSetToNode); // setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect) _keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); @@ -215,6 +218,11 @@ void NodeList::reset() { _numNoReplyDomainCheckIns = 0; + // lock and clear our set of ignored IDs + _ignoredSetLock.lockForWrite(); + _ignoredNodeIDs.clear(); + _ignoredSetLock.unlock(); + // refresh the owner UUID to the NULL UUID setSessionUUID(QUuid()); @@ -250,7 +258,6 @@ void NodeList::sendDomainServerCheckIn() { qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in."; handleICEConnectionToDomainServer(); } else if (!_domainHandler.getIP().isNull()) { - bool isUsingDTLS = false; PacketType domainPacketType = !_domainHandler.isConnected() ? PacketType::DomainConnectRequest : PacketType::DomainListRequest; @@ -273,7 +280,7 @@ void NodeList::sendDomainServerCheckIn() { } // check if we're missing a keypair we need to verify ourselves with the domain-server - auto& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); const QUuid& connectionToken = _domainHandler.getConnectionToken(); // we assume that we're on the same box as the DS if it has the same local address and @@ -283,10 +290,10 @@ void NodeList::sendDomainServerCheckIn() { bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull() && !localhostDomain; - if (requiresUsernameSignature && !accountManager.getAccountInfo().hasPrivateKey()) { + if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) { qWarning() << "A keypair is required to present a username signature to the domain-server" << "but no keypair is present. Waiting for keypair generation to complete."; - accountManager.generateNewUserKeypair(); + accountManager->generateNewUserKeypair(); // don't send the check in packet - wait for the keypair first return; @@ -297,6 +304,13 @@ void NodeList::sendDomainServerCheckIn() { QDataStream packetStream(domainPacket.get()); if (domainPacketType == PacketType::DomainConnectRequest) { + +#if (PR_BUILD || DEV_BUILD) + if (_shouldSendNewerVersion) { + domainPacket->setVersion(versionForPacketType(domainPacketType) + 1); + } +#endif + QUuid connectUUID; if (!_domainHandler.getAssignmentUUID().isNull()) { @@ -312,27 +326,31 @@ void NodeList::sendDomainServerCheckIn() { // pack the connect UUID for this connect request packetStream << connectUUID; + + // include the protocol version signature in our connect request + QByteArray protocolVersionSig = protocolVersionsSignature(); + packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size()); } - // pack our data to send to the domain-server + // pack our data to send to the domain-server including + // the hostname information (so the domain-server can see which place name we came in on) packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); + packetStream << DependencyManager::get()->getPlaceName(); if (!_domainHandler.isConnected()) { - DataServerAccountInfo& accountInfo = accountManager.getAccountInfo(); + DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); packetStream << accountInfo.getUsername(); // if this is a connect request, and we can present a username signature, send it along - if (requiresUsernameSignature && accountManager.getAccountInfo().hasPrivateKey()) { - const QByteArray& usernameSignature = accountManager.getAccountInfo().getUsernameSignature(connectionToken); + if (requiresUsernameSignature && accountManager->getAccountInfo().hasPrivateKey()) { + const QByteArray& usernameSignature = accountManager->getAccountInfo().getUsernameSignature(connectionToken); packetStream << usernameSignature; } } flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn); - if (!isUsingDTLS) { - sendPacket(std::move(domainPacket), _domainHandler.getSockAddr()); - } + sendPacket(std::move(domainPacket), _domainHandler.getSockAddr()); if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS @@ -451,7 +469,7 @@ void NodeList::handleICEConnectionToDomainServer() { LimitedNodeList::sendPeerQueryToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), - _domainHandler.getICEDomainID()); + _domainHandler.getPendingDomainID()); } } @@ -464,7 +482,7 @@ void NodeList::pingPunchForDomainServer() { if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) { qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" - << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); + << uuidStringWithoutCurlyBraces(_domainHandler.getPendingDomainID()); } else { if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) { // if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat @@ -503,6 +521,7 @@ void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer message) { if (_domainHandler.getSockAddr().isNull()) { + qWarning() << "IGNORING DomainList packet while not connected to a Domain Server"; // refuse to process this packet if we aren't currently connected to the DS return; } @@ -516,7 +535,7 @@ void NodeList::processDomainServerList(QSharedPointer message) DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList); QDataStream packetStream(message->getMessage()); - + // grab the domain's ID from the beginning of the packet QUuid domainUUID; packetStream >> domainUUID; @@ -525,6 +544,10 @@ void NodeList::processDomainServerList(QSharedPointer message) if (!_domainHandler.isConnected()) { _domainHandler.setUUID(domainUUID); _domainHandler.setIsConnected(true); + } else if (_domainHandler.getUUID() != domainUUID) { + // Recieved packet from different domain. + qWarning() << "IGNORING DomainList packet from" << domainUUID << "while connected to" << _domainHandler.getUUID(); + return; } // pull our owner UUID from the packet, it's always the first thing @@ -532,14 +555,11 @@ void NodeList::processDomainServerList(QSharedPointer message) packetStream >> newUUID; setSessionUUID(newUUID); - quint8 isAllowedEditor; - packetStream >> isAllowedEditor; - setIsAllowedEditor((bool) isAllowedEditor); + // pull the permissions/right/privileges for this node out of the stream + NodePermissions newPermissions; + packetStream >> newPermissions; + setPermissions(newPermissions); - quint8 thisNodeCanRez; - packetStream >> thisNodeCanRez; - setThisNodeCanRez((bool) thisNodeCanRez); - // pull each node in the packet while (packetStream.device()->pos() < message->getSize()) { parseNodeFromPacketStream(packetStream); @@ -566,10 +586,9 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { qint8 nodeType; QUuid nodeUUID, connectionUUID; HifiSockAddr nodePublicSocket, nodeLocalSocket; - bool isAllowedEditor; - bool canRez; + NodePermissions permissions; - packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> isAllowedEditor >> canRez; + packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> permissions; // if the public socket address is 0 then it's reachable at the same IP // as the domain server @@ -580,8 +599,7 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { packetStream >> connectionUUID; SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, - nodeLocalSocket, isAllowedEditor, canRez, - connectionUUID); + nodeLocalSocket, permissions, connectionUUID); } void NodeList::sendAssignment(Assignment& assignment) { @@ -682,3 +700,67 @@ void NodeList::sendKeepAlivePings() { sendPacket(constructPingPacket(), *node); }); } + +void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) { + // enumerate the nodes to send a reliable ignore packet to each that can leverage it + + if (!nodeID.isNull() && _sessionUUID != nodeID) { + eachMatchingNode([&nodeID](const SharedNodePointer& node)->bool { + if (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer) { + return true; + } else { + return false; + } + }, [&nodeID, this](const SharedNodePointer& destinationNode) { + // create a reliable NLPacket with space for the ignore UUID + auto ignorePacket = NLPacket::create(PacketType::NodeIgnoreRequest, NUM_BYTES_RFC4122_UUID, true); + + // write the node ID to the packet + ignorePacket->write(nodeID.toRfc4122()); + + qDebug() << "Sending packet to ignore node" << uuidStringWithoutCurlyBraces(nodeID); + + // send off this ignore packet reliably to the matching node + sendPacket(std::move(ignorePacket), *destinationNode); + }); + + QReadLocker setLocker { &_ignoredSetLock }; + + // add this nodeID to our set of ignored IDs + _ignoredNodeIDs.insert(nodeID); + + emit ignoredNode(nodeID); + + } else { + qWarning() << "UsersScriptingInterface::ignore called with an invalid ID or an ID which matches the current session ID."; + } +} + +bool NodeList::isIgnoringNode(const QUuid& nodeID) const { + QReadLocker setLocker { &_ignoredSetLock }; + return _ignoredNodeIDs.find(nodeID) != _ignoredNodeIDs.cend(); +} + +void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) { + if (newNode->getType() == NodeType::AudioMixer || newNode->getType() == NodeType::AvatarMixer) { + // this is a mixer that we just added - it's unlikely it knows who we were previously ignoring in this session, + // so send that list along now (assuming it isn't empty) + + QReadLocker setLocker { &_ignoredSetLock }; + + if (_ignoredNodeIDs.size() > 0) { + // setup a packet list so we can send the stream of ignore IDs + auto ignorePacketList = NLPacketList::create(PacketType::NodeIgnoreRequest, QByteArray(), true); + + // enumerate the ignored IDs and write them to the packet list + auto it = _ignoredNodeIDs.cbegin(); + while (it != _ignoredNodeIDs.end()) { + ignorePacketList->write(it->toRfc4122()); + ++it; + } + + // send this NLPacketList to the new node + sendPacketList(std::move(ignorePacketList), *newNode); + } + } +} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 4b196d5f7b..ff994ce612 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -20,6 +20,8 @@ #include // not on windows, not needed for mac or windows #endif +#include + #include #include #include @@ -68,6 +70,9 @@ public: void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; } + void ignoreNodeBySessionID(const QUuid& nodeID); + bool isIgnoringNode(const QUuid& nodeID) const; + public slots: void reset(); void sendDomainServerCheckIn(); @@ -85,9 +90,15 @@ public slots: void processICEPingPacket(QSharedPointer message); +#if (PR_BUILD || DEV_BUILD) + void toggleSendNewerDSConnectVersion(bool shouldSendNewerVersion) { _shouldSendNewerVersion = shouldSendNewerVersion; } +#endif + signals: void limitOfSilentDomainCheckInsReached(); void receivedDomainServerList(); + void ignoredNode(const QUuid& nodeID); + private slots: void stopKeepalivePingTimer(); void sendPendingDSPathQuery(); @@ -99,6 +110,9 @@ private slots: void pingPunchForDomainServer(); void sendKeepAlivePings(); + + void maybeSendIgnoreSetToNode(SharedNodePointer node); + private: NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); @@ -123,6 +137,13 @@ private: HifiSockAddr _assignmentServerSocket; bool _isShuttingDown { false }; QTimer _keepAlivePingTimer; + + mutable QReadWriteLock _ignoredSetLock; + tbb::concurrent_unordered_set _ignoredNodeIDs; + +#if (PR_BUILD || DEV_BUILD) + bool _shouldSendNewerVersion { false }; +#endif }; #endif // hifi_NodeList_h diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp new file mode 100644 index 0000000000..fb74ccdc94 --- /dev/null +++ b/libraries/networking/src/NodePermissions.cpp @@ -0,0 +1,97 @@ +// +// NodePermissions.cpp +// libraries/networking/src/ +// +// Created by Seth Alves on 2016-6-1. +// Copyright 2016 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 "NodePermissions.h" + +QString NodePermissions::standardNameLocalhost = QString("localhost"); +QString NodePermissions::standardNameLoggedIn = QString("logged-in"); +QString NodePermissions::standardNameAnonymous = QString("anonymous"); + +QStringList NodePermissions::standardNames = QList() + << NodePermissions::standardNameLocalhost + << NodePermissions::standardNameLoggedIn + << NodePermissions::standardNameAnonymous; + +NodePermissions& NodePermissions::operator|=(const NodePermissions& rhs) { + this->canConnectToDomain |= rhs.canConnectToDomain; + this->canAdjustLocks |= rhs.canAdjustLocks; + this->canRezPermanentEntities |= rhs.canRezPermanentEntities; + this->canRezTemporaryEntities |= rhs.canRezTemporaryEntities; + this->canWriteToAssetServer |= rhs.canWriteToAssetServer; + this->canConnectPastMaxCapacity |= rhs.canConnectPastMaxCapacity; + return *this; +} +NodePermissions& NodePermissions::operator|=(const NodePermissionsPointer& rhs) { + if (rhs) { + *this |= *rhs.get(); + } + return *this; +} +NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs) { + if (lhs && rhs) { + *lhs.get() |= rhs; + } + return lhs; +} + + +QDataStream& operator<<(QDataStream& out, const NodePermissions& perms) { + out << perms.canConnectToDomain; + out << perms.canAdjustLocks; + out << perms.canRezPermanentEntities; + out << perms.canRezTemporaryEntities; + out << perms.canWriteToAssetServer; + out << perms.canConnectPastMaxCapacity; + return out; +} + +QDataStream& operator>>(QDataStream& in, NodePermissions& perms) { + in >> perms.canConnectToDomain; + in >> perms.canAdjustLocks; + in >> perms.canRezPermanentEntities; + in >> perms.canRezTemporaryEntities; + in >> perms.canWriteToAssetServer; + in >> perms.canConnectPastMaxCapacity; + return in; +} + +QDebug operator<<(QDebug debug, const NodePermissions& perms) { + debug.nospace() << "[permissions: " << perms.getID() << " --"; + if (perms.canConnectToDomain) { + debug << " connect"; + } + if (perms.canAdjustLocks) { + debug << " locks"; + } + if (perms.canRezPermanentEntities) { + debug << " rez"; + } + if (perms.canRezTemporaryEntities) { + debug << " rez-tmp"; + } + if (perms.canWriteToAssetServer) { + debug << " asset-server"; + } + if (perms.canConnectPastMaxCapacity) { + debug << " ignore-max-cap"; + } + debug.nospace() << "]"; + return debug.nospace(); +} +QDebug operator<<(QDebug debug, const NodePermissionsPointer& perms) { + if (perms) { + return operator<<(debug, *perms.get()); + } + debug.nospace() << "[permissions: null]"; + return debug.nospace(); +} diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h new file mode 100644 index 0000000000..de46a0dae5 --- /dev/null +++ b/libraries/networking/src/NodePermissions.h @@ -0,0 +1,114 @@ +// +// NodePermissions.h +// libraries/networking/src/ +// +// Created by Seth Alves on 2016-6-1. +// Copyright 2016 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_NodePermissions_h +#define hifi_NodePermissions_h + +#include +#include +#include +#include +#include + +class NodePermissions; +using NodePermissionsPointer = std::shared_ptr; + +class NodePermissions { +public: + NodePermissions() { _id = QUuid::createUuid().toString(); } + NodePermissions(const QString& name) { _id = name.toLower(); } + NodePermissions(QMap perms) { + _id = perms["permissions_id"].toString().toLower(); + canConnectToDomain = perms["id_can_connect"].toBool(); + canAdjustLocks = perms["id_can_adjust_locks"].toBool(); + canRezPermanentEntities = perms["id_can_rez"].toBool(); + canRezTemporaryEntities = perms["id_can_rez_tmp"].toBool(); + canWriteToAssetServer = perms["id_can_write_to_asset_server"].toBool(); + canConnectPastMaxCapacity = perms["id_can_connect_past_max_capacity"].toBool(); + } + + QString getID() const { return _id; } + + // the _id member isn't authenticated and _username is. + void setUserName(QString userName) { _userName = userName.toLower(); } + QString getUserName() { return _userName; } + + bool isAssignment { false }; + + // these 3 names have special meaning. + static QString standardNameLocalhost; + static QString standardNameLoggedIn; + static QString standardNameAnonymous; + static QStringList standardNames; + + // the initializations here should match the defaults in describe-settings.json + bool canConnectToDomain { true }; + bool canAdjustLocks { false }; + bool canRezPermanentEntities { false }; + bool canRezTemporaryEntities { false }; + bool canWriteToAssetServer { false }; + bool canConnectPastMaxCapacity { false }; + + void setAll(bool value) { + canConnectToDomain = value; + canAdjustLocks = value; + canRezPermanentEntities = value; + canRezTemporaryEntities = value; + canWriteToAssetServer = value; + canConnectPastMaxCapacity = value; + } + + QVariant toVariant() { + QMap values; + values["permissions_id"] = _id; + values["id_can_connect"] = canConnectToDomain; + values["id_can_adjust_locks"] = canAdjustLocks; + values["id_can_rez"] = canRezPermanentEntities; + values["id_can_rez_tmp"] = canRezTemporaryEntities; + values["id_can_write_to_asset_server"] = canWriteToAssetServer; + values["id_can_connect_past_max_capacity"] = canConnectPastMaxCapacity; + return QVariant(values); + } + + NodePermissions& operator|=(const NodePermissions& rhs); + NodePermissions& operator|=(const NodePermissionsPointer& rhs); + friend QDataStream& operator<<(QDataStream& out, const NodePermissions& perms); + friend QDataStream& operator>>(QDataStream& in, NodePermissions& perms); + +protected: + QString _id; + QString _userName; +}; + + +// wrap QHash in a class that forces all keys to be lowercase +class NodePermissionsMap { +public: + NodePermissionsMap() { } + NodePermissionsPointer& operator[](const QString& key) { return _data[key.toLower()]; } + NodePermissionsPointer operator[](const QString& key) const { return _data.value(key.toLower()); } + bool contains(const QString& key) const { return _data.contains(key.toLower()); } + QList keys() const { return _data.keys(); } + QHash get() { return _data; } + void clear() { _data.clear(); } + +private: + QHash _data; +}; + + +const NodePermissions DEFAULT_AGENT_PERMISSIONS; + +QDebug operator<<(QDebug debug, const NodePermissions& perms); +QDebug operator<<(QDebug debug, const NodePermissionsPointer& perms); +NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs); + +#endif // hifi_NodePermissions_h diff --git a/libraries/networking/src/OAuthNetworkAccessManager.cpp b/libraries/networking/src/OAuthNetworkAccessManager.cpp index fa6f3b8340..92e7a2ff4f 100644 --- a/libraries/networking/src/OAuthNetworkAccessManager.cpp +++ b/libraries/networking/src/OAuthNetworkAccessManager.cpp @@ -32,14 +32,14 @@ OAuthNetworkAccessManager* OAuthNetworkAccessManager::getInstance() { QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest& req, QIODevice* outgoingData) { - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); - if (accountManager.hasValidAccessToken() + if (accountManager->hasValidAccessToken() && req.url().host() == NetworkingConstants::METAVERSE_SERVER_URL.host()) { QNetworkRequest authenticatedRequest(req); authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); authenticatedRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, - accountManager.getAccountInfo().getAccessToken().authorizationHeaderValue()); + accountManager->getAccountInfo().getAccessToken().authorizationHeaderValue()); return QNetworkAccessManager::createRequest(op, authenticatedRequest, outgoingData); } else { diff --git a/libraries/networking/src/ReceivedMessage.cpp b/libraries/networking/src/ReceivedMessage.cpp index 6ef2b7c7d4..cd3eb03473 100644 --- a/libraries/networking/src/ReceivedMessage.cpp +++ b/libraries/networking/src/ReceivedMessage.cpp @@ -14,8 +14,8 @@ #include "QSharedPointer" -static int receivedMessageMetaTypeId = qRegisterMetaType("ReceivedMessage*"); -static int sharedPtrReceivedMessageMetaTypeId = qRegisterMetaType>("QSharedPointer"); +int receivedMessageMetaTypeId = qRegisterMetaType("ReceivedMessage*"); +int sharedPtrReceivedMessageMetaTypeId = qRegisterMetaType>("QSharedPointer"); static const int HEAD_DATA_SIZE = 512; diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 0d7690840d..75e15db2a4 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -17,6 +17,8 @@ #include "NetworkLogging.h" #include "UserActivityLogger.h" +#include +#include "AddressManager.h" static const QString USER_ACTIVITY_URL = "/api/v1/user_activities"; @@ -34,7 +36,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall return; } - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); // Adding the action name @@ -59,7 +61,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall params.errorCallbackMethod = "requestError"; } - accountManager.sendRequest(USER_ACTIVITY_URL, + accountManager->sendRequest(USER_ACTIVITY_URL, AccountManagerAuth::Optional, QNetworkAccessManager::PostOperation, params, NULL, multipart); @@ -124,6 +126,19 @@ void UserActivityLogger::changedDomain(QString domainURL) { } void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceName) { + static QStringList DEVICE_BLACKLIST = { + "Desktop", + "NullDisplayPlugin", + "3D TV - Side by Side Stereo", + "3D TV - Interleaved", + + "Keyboard/Mouse" + }; + + if (DEVICE_BLACKLIST.contains(deviceName)) { + return; + } + const QString ACTION_NAME = "connected_device"; QJsonObject actionDetails; const QString TYPE_OF_DEVICE = "type_of_device"; @@ -147,12 +162,37 @@ void UserActivityLogger::loadedScript(QString scriptName) { } -void UserActivityLogger::wentTo(QString destinationType, QString destinationName) { +void UserActivityLogger::wentTo(AddressManager::LookupTrigger lookupTrigger, QString destinationType, QString destinationName) { + // Only accept these types of triggers. Other triggers are usually used internally in AddressManager. + QString trigger; + switch (lookupTrigger) { + case AddressManager::UserInput: + trigger = "UserInput"; + break; + case AddressManager::Back: + trigger = "Back"; + break; + case AddressManager::Forward: + trigger = "Forward"; + break; + case AddressManager::StartupFromSettings: + trigger = "StartupFromSettings"; + break; + case AddressManager::Suggestions: + trigger = "Suggesions"; + break; + default: + return; + } + + const QString ACTION_NAME = "went_to"; QJsonObject actionDetails; + const QString TRIGGER_TYPE_KEY = "trigger"; const QString DESTINATION_TYPE_KEY = "destination_type"; const QString DESTINATION_NAME_KEY = "detination_name"; + actionDetails.insert(TRIGGER_TYPE_KEY, trigger); actionDetails.insert(DESTINATION_TYPE_KEY, destinationType); actionDetails.insert(DESTINATION_NAME_KEY, destinationName); diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index c2ab93db2f..b41960a8ad 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -20,6 +20,7 @@ #include #include +#include "AddressManager.h" class UserActivityLogger : public QObject { Q_OBJECT @@ -42,7 +43,7 @@ public slots: void changedDomain(QString domainURL); void connectedDevice(QString typeOfDevice, QString deviceName); void loadedScript(QString scriptName); - void wentTo(QString destinationType, QString destinationName); + void wentTo(AddressManager::LookupTrigger trigger, QString destinationType, QString destinationName); private slots: void requestError(QNetworkReply& errorReply); diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp new file mode 100644 index 0000000000..8b22b8ff58 --- /dev/null +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -0,0 +1,31 @@ +// +// UserActivityLoggerScriptingInterface.h +// libraries/networking/src +// +// Created by Ryan Huffman on 6/06/16. +// Copyright 2016 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 "UserActivityLoggerScriptingInterface.h" +#include "UserActivityLogger.h" + +void UserActivityLoggerScriptingInterface::enabledEdit() { + logAction("enabled_edit"); +} + +void UserActivityLoggerScriptingInterface::openedMarketplace() { + logAction("opened_marketplace"); +} + +void UserActivityLoggerScriptingInterface::toggledAway(bool isAway) { + logAction("toggled_away", { { "is_away", isAway } }); +} + +void UserActivityLoggerScriptingInterface::logAction(QString action, QJsonObject details) { + QMetaObject::invokeMethod(&UserActivityLogger::getInstance(), "logAction", + Q_ARG(QString, action), + Q_ARG(QJsonObject, details)); +} diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h new file mode 100644 index 0000000000..9d60d666e2 --- /dev/null +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -0,0 +1,31 @@ +// +// UserActivityLoggerScriptingInterface.h +// libraries/networking/src +// +// Created by Ryan Huffman on 6/06/16. +// Copyright 2016 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_UserActivityLoggerScriptingInterface_h +#define hifi_UserActivityLoggerScriptingInterface_h + +#include +#include + +#include + +class UserActivityLoggerScriptingInterface : public QObject, public Dependency { + Q_OBJECT +public: + Q_INVOKABLE void enabledEdit(); + Q_INVOKABLE void openedMarketplace(); + Q_INVOKABLE void toggledAway(bool isAway); + +private: + void logAction(QString action, QJsonObject details = {}); +}; + +#endif // hifi_UserActivityLoggerScriptingInterface_h diff --git a/libraries/networking/src/udt/BasePacket.cpp b/libraries/networking/src/udt/BasePacket.cpp index dba241f221..8a4b98de87 100644 --- a/libraries/networking/src/udt/BasePacket.cpp +++ b/libraries/networking/src/udt/BasePacket.cpp @@ -152,8 +152,10 @@ QByteArray BasePacket::readWithoutCopy(qint64 maxSize) { qint64 BasePacket::writeString(const QString& string) { QByteArray data = string.toUtf8(); - writePrimitive(static_cast(data.length())); - return writeData(data.constData(), data.length()); + uint32_t length = data.length(); + writePrimitive(length); + write(data.constData(), data.length()); + return length + sizeof(uint32_t); } QString BasePacket::readString() { @@ -173,7 +175,6 @@ bool BasePacket::reset() { } qint64 BasePacket::writeData(const char* data, qint64 maxSize) { - Q_ASSERT_X(maxSize <= bytesAvailableForWrite(), "BasePacket::writeData", "not enough space for write"); // make sure we have the space required to write this block diff --git a/libraries/networking/src/udt/Packet.cpp b/libraries/networking/src/udt/Packet.cpp index d46cae2404..e852332317 100644 --- a/libraries/networking/src/udt/Packet.cpp +++ b/libraries/networking/src/udt/Packet.cpp @@ -19,7 +19,7 @@ using namespace udt; -static int packetMetaTypeId = qRegisterMetaType("Packet*"); +int packetMetaTypeId = qRegisterMetaType("Packet*"); using Key = uint64_t; static const std::array KEYS {{ diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index e4aab94090..ce1f25d45d 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -12,13 +12,15 @@ #include "PacketHeaders.h" #include +#include +#include #include #include Q_DECLARE_METATYPE(PacketType); -static int packetTypeMetaTypeId = qRegisterMetaType(); +int packetTypeMetaTypeId = qRegisterMetaType(); const QSet NON_VERIFIED_PACKETS = QSet() << PacketType::NodeJsonStats << PacketType::EntityQuery @@ -38,19 +40,19 @@ const QSet NON_SOURCED_PACKETS = QSet() << PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode << PacketType::DomainServerRemovedNode; -const QSet RELIABLE_PACKETS = QSet(); - PacketVersion versionForPacketType(PacketType packetType) { switch (packetType) { case PacketType::DomainList: - return 18; + return static_cast(DomainListVersion::PermissionsGrid); case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_LIGHT_HAS_FALLOFF_RADIUS; + return VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS; + case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: - return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); + case PacketType::KillAvatar: + return static_cast(AvatarMixerPacketVersion::AbsoluteSixByteRotations); case PacketType::ICEServerHeartbeat: return 18; // ICE Server Heartbeat signing case PacketType::AssetGetInfo: @@ -58,6 +60,25 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AssetUpload: // Removal of extension from Asset requests return 18; + case PacketType::NodeIgnoreRequest: + return 18; // Introduction of node ignore request (which replaced an unused packet tpye) + + case PacketType::DomainConnectionDenied: + return static_cast(DomainConnectionDeniedVersion::IncludesReasonCode); + + case PacketType::DomainConnectRequest: + return static_cast(DomainConnectRequestVersion::HasProtocolVersions); + + case PacketType::DomainServerAddedNode: + return static_cast(DomainServerAddedNodeVersion::PermissionsGrid); + + case PacketType::MixedAudio: + case PacketType::SilentAudioFrame: + case PacketType::InjectAudio: + case PacketType::MicrophoneAudioNoEcho: + case PacketType::MicrophoneAudioWithEcho: + return static_cast(AudioVersion::CodecNameInAudioPackets); + default: return 17; } @@ -77,3 +98,44 @@ QDebug operator<<(QDebug debug, const PacketType& type) { debug.nospace().noquote() << (uint8_t) type << " (" << typeName << ")"; return debug.space(); } + +#if (PR_BUILD || DEV_BUILD) +static bool sendWrongProtocolVersion = false; +void sendWrongProtocolVersionsSignature(bool sendWrongVersion) { + sendWrongProtocolVersion = sendWrongVersion; +} +#endif + +static QByteArray protocolVersionSignature; +static QString protocolVersionSignatureBase64; +static void ensureProtocolVersionsSignature() { + static std::once_flag once; + std::call_once(once, [&] { + QByteArray buffer; + QDataStream stream(&buffer, QIODevice::WriteOnly); + uint8_t numberOfProtocols = static_cast(PacketType::LAST_PACKET_TYPE) + 1; + stream << numberOfProtocols; + for (uint8_t packetType = 0; packetType < numberOfProtocols; packetType++) { + uint8_t packetTypeVersion = static_cast(versionForPacketType(static_cast(packetType))); + stream << packetTypeVersion; + } + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(buffer); + protocolVersionSignature = hash.result(); + protocolVersionSignatureBase64 = protocolVersionSignature.toBase64(); + }); +} +QByteArray protocolVersionsSignature() { + ensureProtocolVersionsSignature(); + #if (PR_BUILD || DEV_BUILD) + if (sendWrongProtocolVersion) { + return QByteArray("INCORRECTVERSION"); // only for debugging version checking + } + #endif + + return protocolVersionSignature; +} +QString protocolVersionsSignatureBase64() { + ensureProtocolVersionsSignature(); + return protocolVersionSignatureBase64; +} diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index b98a87e439..7281e24fa9 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -61,7 +61,7 @@ public: AssignmentClientStatus, NoisyMute, AvatarIdentity, - AvatarBillboard, + NodeIgnoreRequest, DomainConnectRequest, DomainServerRequireDTLS, NodeJsonStats, @@ -94,7 +94,11 @@ public: ICEServerHeartbeatDenied, AssetMappingOperation, AssetMappingOperationReply, - ICEServerHeartbeatACK + ICEServerHeartbeatACK, + NegotiateAudioFormat, + SelectedAudioFormat, + MoreEntityShapes, + LAST_PACKET_TYPE = MoreEntityShapes }; }; @@ -106,9 +110,14 @@ typedef char PacketVersion; extern const QSet NON_VERIFIED_PACKETS; extern const QSet NON_SOURCED_PACKETS; -extern const QSet RELIABLE_PACKETS; PacketVersion versionForPacketType(PacketType packetType); +QByteArray protocolVersionsSignature(); /// returns a unqiue signature for all the current protocols +QString protocolVersionsSignatureBase64(); + +#if (PR_BUILD || DEV_BUILD) +void sendWrongProtocolVersionsSignature(bool sendWrongVersion); /// for debugging version negotiation +#endif uint qHash(const PacketType& key, uint seed); QDebug operator<<(QDebug debug, const PacketType& type); @@ -171,10 +180,43 @@ const PacketVersion VERSION_ENTITITES_HAVE_QUERY_BOX = 54; const PacketVersion VERSION_ENTITITES_HAVE_COLLISION_MASK = 55; const PacketVersion VERSION_ATMOSPHERE_REMOVED = 56; const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57; +const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58; +const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59; +const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60; +const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61; +const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, - SoftAttachmentSupport + SoftAttachmentSupport, + AvatarEntities, + AbsoluteSixByteRotations +}; + +enum class DomainConnectRequestVersion : PacketVersion { + NoHostname = 17, + HasHostname, + HasProtocolVersions +}; + +enum class DomainConnectionDeniedVersion : PacketVersion { + ReasonMessageOnly = 17, + IncludesReasonCode +}; + +enum class DomainServerAddedNodeVersion : PacketVersion { + PrePermissionsGrid = 17, + PermissionsGrid +}; + +enum class DomainListVersion : PacketVersion { + PrePermissionsGrid = 18, + PermissionsGrid +}; + +enum class AudioVersion : PacketVersion { + HasCompressedAudio = 17, + CodecNameInAudioPackets }; #endif // hifi_PacketHeaders_h diff --git a/libraries/networking/src/udt/PacketList.cpp b/libraries/networking/src/udt/PacketList.cpp index b12b7e6938..ea84548210 100644 --- a/libraries/networking/src/udt/PacketList.cpp +++ b/libraries/networking/src/udt/PacketList.cpp @@ -15,7 +15,7 @@ using namespace udt; -static int packetListMetaTypeId = qRegisterMetaType("PacketList*"); +int packetListMetaTypeId = qRegisterMetaType("PacketList*"); std::unique_ptr PacketList::create(PacketType packetType, QByteArray extendedHeader, bool isReliable, bool isOrdered) { diff --git a/libraries/networking/src/udt/PacketQueue.cpp b/libraries/networking/src/udt/PacketQueue.cpp index 4b8c0b187c..3684e5ba07 100644 --- a/libraries/networking/src/udt/PacketQueue.cpp +++ b/libraries/networking/src/udt/PacketQueue.cpp @@ -65,7 +65,9 @@ void PacketQueue::queuePacket(PacketPointer packet) { } void PacketQueue::queuePacketList(PacketListPointer packetList) { - packetList->preparePackets(getNextMessageNumber()); + if (packetList->isOrdered()) { + packetList->preparePackets(getNextMessageNumber()); + } LockGuard locker(_packetsLock); _channels.push_back(std::move(packetList->_packets)); diff --git a/libraries/octree/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp index cac2211d29..fcbc085339 100644 --- a/libraries/octree/src/JurisdictionMap.cpp +++ b/libraries/octree/src/JurisdictionMap.cpp @@ -17,90 +17,11 @@ #include #include #include -#include #include "OctreeLogging.h" #include "JurisdictionMap.h" - -// standard assignment -// copy assignment -JurisdictionMap& JurisdictionMap::operator=(const JurisdictionMap& other) { - copyContents(other); - return *this; -} - -// Copy constructor -JurisdictionMap::JurisdictionMap(const JurisdictionMap& other) : _rootOctalCode(NULL) { - copyContents(other); -} - -void JurisdictionMap::copyContents(unsigned char* rootCodeIn, const std::vector& endNodesIn) { - unsigned char* rootCode; - std::vector endNodes; - if (rootCodeIn) { - size_t bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(rootCodeIn)); - rootCode = new unsigned char[bytes]; - memcpy(rootCode, rootCodeIn, bytes); - } else { - rootCode = new unsigned char[1]; - *rootCode = 0; - } - - for (size_t i = 0; i < endNodesIn.size(); i++) { - if (endNodesIn[i]) { - size_t bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodesIn[i])); - unsigned char* endNodeCode = new unsigned char[bytes]; - memcpy(endNodeCode, endNodesIn[i], bytes); - endNodes.push_back(endNodeCode); - } - } - init(rootCode, endNodes); -} - -void JurisdictionMap::copyContents(const JurisdictionMap& other) { - _nodeType = other._nodeType; - copyContents(other._rootOctalCode, other._endNodes); -} - -JurisdictionMap::~JurisdictionMap() { - clear(); -} - -void JurisdictionMap::clear() { - if (_rootOctalCode) { - delete[] _rootOctalCode; - _rootOctalCode = NULL; - } - - for (size_t i = 0; i < _endNodes.size(); i++) { - if (_endNodes[i]) { - delete[] _endNodes[i]; - } - } - _endNodes.clear(); -} - -JurisdictionMap::JurisdictionMap(NodeType_t type) : _rootOctalCode(NULL) { - _nodeType = type; - unsigned char* rootCode = new unsigned char[1]; - *rootCode = 0; - - std::vector emptyEndNodes; - init(rootCode, emptyEndNodes); -} - -JurisdictionMap::JurisdictionMap(const char* filename) : _rootOctalCode(NULL) { - clear(); // clean up our own memory - readFromFile(filename); -} - -JurisdictionMap::JurisdictionMap(unsigned char* rootOctalCode, const std::vector& endNodes) - : _rootOctalCode(NULL) { - init(rootOctalCode, endNodes); -} - -void myDebugoutputBits(unsigned char byte, bool withNewLine) { +void myDebugOutputBits(unsigned char byte, bool withNewLine) { if (isalnum(byte)) { printf("[ %d (%c): ", byte, byte); } else { @@ -117,13 +38,12 @@ void myDebugoutputBits(unsigned char byte, bool withNewLine) { } } - void myDebugPrintOctalCode(const unsigned char* octalCode, bool withNewLine) { if (!octalCode) { - printf("NULL"); + printf("nullptr"); } else { for (size_t i = 0; i < bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(octalCode)); i++) { - myDebugoutputBits(octalCode[i],false); + myDebugOutputBits(octalCode[i], false); } } if (withNewLine) { @@ -131,6 +51,55 @@ void myDebugPrintOctalCode(const unsigned char* octalCode, bool withNewLine) { } } +// standard assignment +// copy assignment +JurisdictionMap& JurisdictionMap::operator=(const JurisdictionMap& other) { + copyContents(other); + return *this; +} + +// Copy constructor +JurisdictionMap::JurisdictionMap(const JurisdictionMap& other) : _rootOctalCode(nullptr) { + copyContents(other); +} + +void JurisdictionMap::copyContents(const OctalCodePtr& rootCodeIn, const OctalCodePtrList& endNodesIn) { + OctalCodePtr rootCode = rootCodeIn; + if (!rootCode) { + rootCode = createOctalCodePtr(1); + *rootCode = 0; + } + + OctalCodePtrList emptyEndNodes; + init(rootCode, endNodesIn); +} + +void JurisdictionMap::copyContents(const JurisdictionMap& other) { + _nodeType = other._nodeType; + + OctalCodePtr rootOctalCode; + OctalCodePtrList endNodes; + + std::tie(rootOctalCode, endNodes) = other.getRootAndEndNodeOctalCodes(); + + init(rootOctalCode, endNodes); +} + +JurisdictionMap::~JurisdictionMap() { +} + +JurisdictionMap::JurisdictionMap(NodeType_t type) : _rootOctalCode(nullptr) { + _nodeType = type; + OctalCodePtr rootCode = createOctalCodePtr(1); + *rootCode = 0; + + OctalCodePtrList emptyEndNodes; + init(rootCode, emptyEndNodes); +} + +JurisdictionMap::JurisdictionMap(const char* filename) : _rootOctalCode(nullptr) { + readFromFile(filename); +} JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHexCodes) { @@ -139,8 +108,8 @@ JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHe _rootOctalCode = hexStringToOctalCode(QString(rootHexCode)); - qCDebug(octree, "JurisdictionMap::JurisdictionMap() _rootOctalCode=%p octalCode=", _rootOctalCode); - myDebugPrintOctalCode(_rootOctalCode, true); + qCDebug(octree, "JurisdictionMap::JurisdictionMap() _rootOctalCode=%p octalCode=", _rootOctalCode.get()); + myDebugPrintOctalCode(_rootOctalCode.get(), true); QString endNodesHexStrings(endNodesHexCodes); QString delimiterPattern(","); @@ -149,7 +118,7 @@ JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHe for (int i = 0; i < endNodeList.size(); i++) { QString endNodeHexString = endNodeList.at(i); - unsigned char* endNodeOctcode = hexStringToOctalCode(endNodeHexString); + auto endNodeOctcode = hexStringToOctalCode(endNodeHexString); qCDebug(octree, "JurisdictionMap::JurisdictionMap() endNodeList(%d)=%s", i, endNodeHexString.toLocal8Bit().constData()); @@ -157,15 +126,29 @@ JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHe //printOctalCode(endNodeOctcode); _endNodes.push_back(endNodeOctcode); - qCDebug(octree, "JurisdictionMap::JurisdictionMap() endNodeOctcode=%p octalCode=", endNodeOctcode); - myDebugPrintOctalCode(endNodeOctcode, true); + qCDebug(octree, "JurisdictionMap::JurisdictionMap() endNodeOctcode=%p octalCode=", endNodeOctcode.get()); + myDebugPrintOctalCode(endNodeOctcode.get(), true); } } +std::tuple JurisdictionMap::getRootAndEndNodeOctalCodes() const { + std::lock_guard lock(_octalCodeMutex); + return std::tuple(_rootOctalCode, _endNodes); +} -void JurisdictionMap::init(unsigned char* rootOctalCode, const std::vector& endNodes) { - clear(); // clean up our own memory +OctalCodePtr JurisdictionMap::getRootOctalCode() const { + std::lock_guard lock(_octalCodeMutex); + return _rootOctalCode; +} + +OctalCodePtrList JurisdictionMap::getEndNodeOctalCodes() const { + std::lock_guard lock(_octalCodeMutex); + return _endNodes; +} + +void JurisdictionMap::init(OctalCodePtr rootOctalCode, const OctalCodePtrList& endNodes) { + std::lock_guard lock(_octalCodeMutex); _rootOctalCode = rootOctalCode; _endNodes = endNodes; } @@ -173,17 +156,19 @@ void JurisdictionMap::init(unsigned char* rootOctalCode, const std::vector lock(_octalCodeMutex); + // if the node is an ancestor of my root, then we return ABOVE - if (isAncestorOf(nodeOctalCode, _rootOctalCode)) { + if (isAncestorOf(nodeOctalCode, _rootOctalCode.get())) { return ABOVE; } // otherwise... - bool isInJurisdiction = isAncestorOf(_rootOctalCode, nodeOctalCode, childIndex); + bool isInJurisdiction = isAncestorOf(_rootOctalCode.get(), nodeOctalCode, childIndex); // if we're under the root, then we can't be under any of the endpoints if (isInJurisdiction) { for (size_t i = 0; i < _endNodes.size(); i++) { - bool isUnderEndNode = isAncestorOf(_endNodes[i], nodeOctalCode); + bool isUnderEndNode = isAncestorOf(_endNodes[i].get(), nodeOctalCode); if (isUnderEndNode) { isInJurisdiction = false; break; @@ -200,8 +185,9 @@ bool JurisdictionMap::readFromFile(const char* filename) { QString rootCode = settings.value("root","00").toString(); qCDebug(octree) << "rootCode=" << rootCode; + std::lock_guard lock(_octalCodeMutex); _rootOctalCode = hexStringToOctalCode(rootCode); - printOctalCode(_rootOctalCode); + printOctalCode(_rootOctalCode.get()); settings.beginGroup("endNodes"); const QStringList childKeys = settings.childKeys(); @@ -211,8 +197,8 @@ bool JurisdictionMap::readFromFile(const char* filename) { values.insert(childKey, childValue); qCDebug(octree) << childKey << "=" << childValue; - unsigned char* octcode = hexStringToOctalCode(childValue); - printOctalCode(octcode); + auto octcode = hexStringToOctalCode(childValue); + printOctalCode(octcode.get()); _endNodes.push_back(octcode); } @@ -221,12 +207,14 @@ bool JurisdictionMap::readFromFile(const char* filename) { } void JurisdictionMap::displayDebugDetails() const { - QString rootNodeValue = octalCodeToHexString(_rootOctalCode); + std::lock_guard lock(_octalCodeMutex); + + QString rootNodeValue = octalCodeToHexString(_rootOctalCode.get()); qCDebug(octree) << "root:" << rootNodeValue; for (size_t i = 0; i < _endNodes.size(); i++) { - QString value = octalCodeToHexString(_endNodes[i]); + QString value = octalCodeToHexString(_endNodes[i].get()); qCDebug(octree) << "End node[" << i << "]: " << rootNodeValue; } } @@ -236,15 +224,16 @@ bool JurisdictionMap::writeToFile(const char* filename) { QString settingsFile(filename); QSettings settings(settingsFile, QSettings::IniFormat); + std::lock_guard lock(_octalCodeMutex); - QString rootNodeValue = octalCodeToHexString(_rootOctalCode); + QString rootNodeValue = octalCodeToHexString(_rootOctalCode.get()); settings.setValue("root", rootNodeValue); settings.beginGroup("endNodes"); for (size_t i = 0; i < _endNodes.size(); i++) { QString key = QString("endnode%1").arg(i); - QString value = octalCodeToHexString(_endNodes[i]); + QString value = octalCodeToHexString(_endNodes[i].get()); settings.setValue(key, value); } settings.endGroup(); @@ -271,18 +260,19 @@ std::unique_ptr JurisdictionMap::packIntoPacket() { packet->writePrimitive(type); // add the root jurisdiction + std::lock_guard lock(_octalCodeMutex); if (_rootOctalCode) { - size_t bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_rootOctalCode)); + size_t bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_rootOctalCode.get())); // No root or end node details to pack! packet->writePrimitive(bytes); - packet->write(reinterpret_cast(_rootOctalCode), bytes); + packet->write(reinterpret_cast(_rootOctalCode.get()), bytes); // if and only if there's a root jurisdiction, also include the end nodes int endNodeCount = (int)_endNodes.size(); packet->writePrimitive(endNodeCount); for (int i=0; i < endNodeCount; i++) { - unsigned char* endNodeCode = _endNodes[i]; + auto endNodeCode = _endNodes[i].get(); size_t bytes = 0; if (endNodeCode) { bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); @@ -299,15 +289,17 @@ std::unique_ptr JurisdictionMap::packIntoPacket() { } int JurisdictionMap::unpackFromPacket(ReceivedMessage& message) { - clear(); - // read the root jurisdiction int bytes = 0; message.readPrimitive(&bytes); + std::lock_guard lock(_octalCodeMutex); + _rootOctalCode = nullptr; + _endNodes.clear(); + if (bytes > 0 && bytes <= message.getBytesLeftToRead()) { - _rootOctalCode = new unsigned char[bytes]; - message.read(reinterpret_cast(_rootOctalCode), bytes); + _rootOctalCode = createOctalCodePtr(bytes); + message.read(reinterpret_cast(_rootOctalCode.get()), bytes); // if and only if there's a root jurisdiction, also include the end nodes int endNodeCount = 0; @@ -318,8 +310,8 @@ int JurisdictionMap::unpackFromPacket(ReceivedMessage& message) { message.readPrimitive(&bytes); if (bytes <= message.getBytesLeftToRead()) { - unsigned char* endNodeCode = new unsigned char[bytes]; - message.read(reinterpret_cast(endNodeCode), bytes); + auto endNodeCode = createOctalCodePtr(bytes); + message.read(reinterpret_cast(endNodeCode.get()), bytes); // if the endNodeCode was 0 length then don't add it if (bytes > 0) { diff --git a/libraries/octree/src/JurisdictionMap.h b/libraries/octree/src/JurisdictionMap.h index fb217e59db..b5a311c3d9 100644 --- a/libraries/octree/src/JurisdictionMap.h +++ b/libraries/octree/src/JurisdictionMap.h @@ -23,6 +23,7 @@ #include #include +#include class JurisdictionMap { public: @@ -41,8 +42,8 @@ public: // application constructors JurisdictionMap(const char* filename); - JurisdictionMap(unsigned char* rootOctalCode, const std::vector& endNodes); JurisdictionMap(const char* rootHextString, const char* endNodesHextString); + ~JurisdictionMap(); Area isMyJurisdiction(const unsigned char* nodeOctalCode, int childIndex) const; @@ -50,11 +51,12 @@ public: bool writeToFile(const char* filename); bool readFromFile(const char* filename); - unsigned char* getRootOctalCode() const { return _rootOctalCode; } - unsigned char* getEndNodeOctalCode(int index) const { return _endNodes[index]; } - int getEndNodeCount() const { return (int)_endNodes.size(); } + // Provide an atomic way to get both the rootOctalCode and endNodeOctalCodes. + std::tuple getRootAndEndNodeOctalCodes() const; + OctalCodePtr getRootOctalCode() const; + OctalCodePtrList getEndNodeOctalCodes() const; - void copyContents(unsigned char* rootCodeIn, const std::vector& endNodesIn); + void copyContents(const OctalCodePtr& rootCodeIn, const OctalCodePtrList& endNodesIn); int unpackFromPacket(ReceivedMessage& message); std::unique_ptr packIntoPacket(); @@ -69,11 +71,11 @@ public: private: void copyContents(const JurisdictionMap& other); // use assignment instead - void clear(); - void init(unsigned char* rootOctalCode, const std::vector& endNodes); + void init(OctalCodePtr rootOctalCode, const OctalCodePtrList& endNodes); - unsigned char* _rootOctalCode; - std::vector _endNodes; + mutable std::mutex _octalCodeMutex; + OctalCodePtr _rootOctalCode { nullptr }; + OctalCodePtrList _endNodes; NodeType_t _nodeType; }; diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 39be760944..d8c8229ce3 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -1863,29 +1863,31 @@ bool Octree::readJSONFromStream(unsigned long streamLength, QDataStream& inputSt QJsonDocument asDocument = QJsonDocument::fromJson(jsonBuffer); QVariant asVariant = asDocument.toVariant(); QVariantMap asMap = asVariant.toMap(); - readFromMap(asMap); + bool success = readFromMap(asMap); delete[] rawData; - return true; + return success; } -void Octree::writeToFile(const char* fileName, OctreeElementPointer element, QString persistAsFileType) { +bool Octree::writeToFile(const char* fileName, OctreeElementPointer element, QString persistAsFileType) { // make the sure file extension makes sense QString qFileName = fileNameWithoutExtension(QString(fileName), PERSIST_EXTENSIONS) + "." + persistAsFileType; QByteArray byteArray = qFileName.toUtf8(); const char* cFileName = byteArray.constData(); + bool success = false; if (persistAsFileType == "svo") { - writeToSVOFile(fileName, element); + success = writeToSVOFile(fileName, element); } else if (persistAsFileType == "json") { - writeToJSONFile(cFileName, element); + success = writeToJSONFile(cFileName, element); } else if (persistAsFileType == "json.gz") { - writeToJSONFile(cFileName, element, true); + success = writeToJSONFile(cFileName, element, true); } else { qCDebug(octree) << "unable to write octree to file of type" << persistAsFileType; } + return success; } -void Octree::writeToJSONFile(const char* fileName, OctreeElementPointer element, bool doGzip) { +bool Octree::writeToJSONFile(const char* fileName, OctreeElementPointer element, bool doGzip) { QVariantMap entityDescription; qCDebug(octree, "Saving JSON SVO to file %s...", fileName); @@ -1906,7 +1908,7 @@ void Octree::writeToJSONFile(const char* fileName, OctreeElementPointer element, bool entityDescriptionSuccess = writeToMap(entityDescription, top, true, true); if (!entityDescriptionSuccess) { qCritical("Failed to convert Entities to QVariantMap while saving to json."); - return; + return false; } // convert the QVariantMap to JSON @@ -1916,22 +1918,26 @@ void Octree::writeToJSONFile(const char* fileName, OctreeElementPointer element, if (doGzip) { if (!gzip(jsonData, jsonDataForFile, -1)) { qCritical("unable to gzip data while saving to json."); - return; + return false; } } else { jsonDataForFile = jsonData; } QFile persistFile(fileName); + bool success = false; if (persistFile.open(QIODevice::WriteOnly)) { - persistFile.write(jsonDataForFile); + success = persistFile.write(jsonDataForFile) != -1; } else { qCritical("Could not write to JSON description of entities."); } + + return success; } -void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element) { - qWarning() << "SVO file format depricated. Support for reading SVO files is no longer support and will be removed soon."; +bool Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element) { + qWarning() << "SVO file format deprecated. Support for reading SVO files is no longer support and will be removed soon."; + bool success = false; std::ofstream file(fileName, std::ios::out|std::ios::binary); @@ -2010,8 +2016,12 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element) } releaseSceneEncodeData(&extraEncodeData); + + success = true; } file.close(); + + return success; } unsigned long Octree::getOctreeElementsCount() { diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 2f0ce3b807..6894f0aa1a 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -299,9 +299,9 @@ public: void loadOctreeFile(const char* fileName); // Octree exporters - void writeToFile(const char* filename, OctreeElementPointer element = NULL, QString persistAsFileType = "svo"); - void writeToJSONFile(const char* filename, OctreeElementPointer element = NULL, bool doGzip = false); - void writeToSVOFile(const char* filename, OctreeElementPointer element = NULL); + bool writeToFile(const char* filename, OctreeElementPointer element = NULL, QString persistAsFileType = "svo"); + bool writeToJSONFile(const char* filename, OctreeElementPointer element = NULL, bool doGzip = false); + bool writeToSVOFile(const char* filename, OctreeElementPointer element = NULL); virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, bool skipThoseWithBadParents) = 0; diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index 4a0a76cd02..28d3794a54 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -78,12 +78,12 @@ void OctreeHeadlessViewer::queryOctree() { } const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - unsigned char* rootCode = map.getRootOctalCode(); + auto rootCode = map.getRootOctalCode(); if (!rootCode) { return; } - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); foundRootDetails = true; }); @@ -146,7 +146,7 @@ void OctreeHeadlessViewer::queryOctree() { } const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - unsigned char* rootCode = map.getRootOctalCode(); + auto rootCode = map.getRootOctalCode(); if (!rootCode) { if (wantExtraDebugging) { @@ -154,7 +154,7 @@ void OctreeHeadlessViewer::queryOctree() { } return; } - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); foundRootDetails = true; }); diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 578d35b687..fdaa6b4928 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -110,29 +110,21 @@ void OctreeSceneStats::copyFromOther(const OctreeSceneStats& other) { _treesRemoved = other._treesRemoved; // before copying the jurisdictions, delete any current values... - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } - for (size_t i = 0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } + _jurisdictionRoot = nullptr; _jurisdictionEndNodes.clear(); // Now copy the values from the other if (other._jurisdictionRoot) { - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(other._jurisdictionRoot)); - _jurisdictionRoot = new unsigned char[bytes]; - memcpy(_jurisdictionRoot, other._jurisdictionRoot, bytes); + auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(other._jurisdictionRoot.get())); + _jurisdictionRoot = createOctalCodePtr(bytes); + memcpy(_jurisdictionRoot.get(), other._jurisdictionRoot.get(), bytes); } for (size_t i = 0; i < other._jurisdictionEndNodes.size(); i++) { - unsigned char* endNodeCode = other._jurisdictionEndNodes[i]; + auto& endNodeCode = other._jurisdictionEndNodes[i]; if (endNodeCode) { - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); - unsigned char* endNodeCodeCopy = new unsigned char[bytes]; - memcpy(endNodeCodeCopy, endNodeCode, bytes); + auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode.get())); + auto endNodeCodeCopy = createOctalCodePtr(bytes); + memcpy(endNodeCodeCopy.get(), endNodeCode.get(), bytes); _jurisdictionEndNodes.push_back(endNodeCodeCopy); } } @@ -162,37 +154,12 @@ void OctreeSceneStats::sceneStarted(bool isFullScene, bool isMoving, OctreeEleme _isFullScene = isFullScene; _isMoving = isMoving; - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } - // clear existing endNodes before copying new ones... - for (size_t i=0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } - _jurisdictionEndNodes.clear(); - // setup jurisdictions if (jurisdictionMap) { - unsigned char* jurisdictionRoot = jurisdictionMap->getRootOctalCode(); - if (jurisdictionRoot) { - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(jurisdictionRoot)); - _jurisdictionRoot = new unsigned char[bytes]; - memcpy(_jurisdictionRoot, jurisdictionRoot, bytes); - } - - // copy new endNodes... - for (int i = 0; i < jurisdictionMap->getEndNodeCount(); i++) { - unsigned char* endNodeCode = jurisdictionMap->getEndNodeOctalCode(i); - if (endNodeCode) { - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); - unsigned char* endNodeCodeCopy = new unsigned char[bytes]; - memcpy(endNodeCodeCopy, endNodeCode, bytes); - _jurisdictionEndNodes.push_back(endNodeCodeCopy); - } - } + std::tie(_jurisdictionRoot, _jurisdictionEndNodes) = jurisdictionMap->getRootAndEndNodeOctalCodes(); + } else { + _jurisdictionRoot = nullptr; + _jurisdictionEndNodes.clear(); } } @@ -270,15 +237,7 @@ void OctreeSceneStats::reset() { _existsInPacketBitsWritten = 0; _treesRemoved = 0; - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } - for (size_t i = 0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } + _jurisdictionRoot = nullptr; _jurisdictionEndNodes.clear(); } @@ -418,9 +377,9 @@ int OctreeSceneStats::packIntoPacket() { // add the root jurisdiction if (_jurisdictionRoot) { // copy the - int bytes = (int)bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_jurisdictionRoot)); + int bytes = (int)bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_jurisdictionRoot.get())); _statsPacket->writePrimitive(bytes); - _statsPacket->write(reinterpret_cast(_jurisdictionRoot), bytes); + _statsPacket->write(reinterpret_cast(_jurisdictionRoot.get()), bytes); // if and only if there's a root jurisdiction, also include the end elements int endNodeCount = (int)_jurisdictionEndNodes.size(); @@ -428,10 +387,10 @@ int OctreeSceneStats::packIntoPacket() { _statsPacket->writePrimitive(endNodeCount); for (int i=0; i < endNodeCount; i++) { - unsigned char* endNodeCode = _jurisdictionEndNodes[i]; - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); + auto& endNodeCode = _jurisdictionEndNodes[i]; + auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode.get())); _statsPacket->writePrimitive(bytes); - _statsPacket->write(reinterpret_cast(endNodeCode), bytes); + _statsPacket->write(reinterpret_cast(endNodeCode.get()), bytes); } } else { int bytes = 0; @@ -500,17 +459,7 @@ int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { packet.readPrimitive(&_existsInPacketBitsWritten); packet.readPrimitive(&_treesRemoved); // before allocating new juridiction, clean up existing ones - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } - - // clear existing endNodes before copying new ones... - for (size_t i = 0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } + _jurisdictionRoot = nullptr; _jurisdictionEndNodes.clear(); // read the root jurisdiction @@ -518,11 +467,11 @@ int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { packet.readPrimitive(&bytes); if (bytes == 0) { - _jurisdictionRoot = NULL; + _jurisdictionRoot = nullptr; _jurisdictionEndNodes.clear(); } else { - _jurisdictionRoot = new unsigned char[bytes]; - packet.read(reinterpret_cast(_jurisdictionRoot), bytes); + _jurisdictionRoot = createOctalCodePtr(bytes); + packet.read(reinterpret_cast(_jurisdictionRoot.get()), bytes); // if and only if there's a root jurisdiction, also include the end elements _jurisdictionEndNodes.clear(); @@ -535,8 +484,8 @@ int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { packet.readPrimitive(&bytes); - unsigned char* endNodeCode = new unsigned char[bytes]; - packet.read(reinterpret_cast(endNodeCode), bytes); + auto endNodeCode = createOctalCodePtr(bytes); + packet.read(reinterpret_cast(endNodeCode.get()), bytes); _jurisdictionEndNodes.push_back(endNodeCode); } diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h index 11aa0b82b2..e7d697f785 100644 --- a/libraries/octree/src/OctreeSceneStats.h +++ b/libraries/octree/src/OctreeSceneStats.h @@ -20,6 +20,7 @@ #include "JurisdictionMap.h" #include "OctreePacketData.h" #include "SequenceNumberStats.h" +#include "OctalCode.h" #define GREENISH 0x40ff40d0 #define YELLOWISH 0xffef40c0 @@ -143,10 +144,10 @@ public: const char* getItemValue(Item item); /// Returns OctCode for root element of the jurisdiction of this particular octree server - unsigned char* getJurisdictionRoot() const { return _jurisdictionRoot; } + OctalCodePtr getJurisdictionRoot() const { return _jurisdictionRoot; } /// Returns list of OctCodes for end elements of the jurisdiction of this particular octree server - const std::vector& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; } + const OctalCodePtrList& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; } bool isMoving() const { return _isMoving; } bool isFullScene() const { return _isFullScene; } @@ -277,8 +278,8 @@ private: static const int MAX_ITEM_VALUE_LENGTH = 128; char _itemValueBuffer[MAX_ITEM_VALUE_LENGTH]; - unsigned char* _jurisdictionRoot; - std::vector _jurisdictionEndNodes; + OctalCodePtr _jurisdictionRoot; + std::vector _jurisdictionEndNodes; }; /// Map between element IDs and their reported OctreeSceneStats. Typically used by classes that need to know which elements sent diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 9ef059bb05..30fb3536ca 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -264,6 +264,10 @@ void CharacterController::setState(State desiredState, const char* reason) { #else void CharacterController::setState(State desiredState) { #endif + if (!_flyingAllowed && desiredState == State::Hover) { + desiredState = State::InAir; + } + if (desiredState != _state) { #ifdef DEBUG_STATE_CHANGE qCDebug(physics) << "CharacterController::setState" << stateToStr(desiredState) << "from" << stateToStr(_state) << "," << reason; @@ -397,11 +401,10 @@ glm::vec3 CharacterController::getLinearVelocity() const { } glm::vec3 CharacterController::getVelocityChange() const { - glm::vec3 velocity(0.0f); if (_rigidBody) { - velocity = bulletToGLM(_rigidBody->getLinearVelocity()); + return bulletToGLM(_velocityChange); } - return velocity; + return glm::vec3(0.0f); } void CharacterController::clearMotors() { @@ -644,3 +647,13 @@ bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPositio avatarRigidBodyRotation = bulletToGLM(worldTrans.getRotation()); return true; } + +void CharacterController::setFlyingAllowed(bool value) { + if (_flyingAllowed != value) { + _flyingAllowed = value; + + if (!_flyingAllowed && _state == State::Hover) { + SET_STATE(State::InAir, "flying not allowed"); + } + } +} diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 0d73289cb0..586ea175e6 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -105,11 +106,15 @@ public: void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale); + bool isEnabled() const { return _enabled; } // thread-safe void setEnabled(bool enabled); - bool isEnabled() const { return _enabled && _dynamicsWorld; } + bool isEnabledAndReady() const { return _enabled && _dynamicsWorld; } bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation); + void setFlyingAllowed(bool value); + + protected: #ifdef DEBUG_STATE_CHANGE void setState(State state, const char* reason); @@ -164,7 +169,7 @@ protected: btQuaternion _followAngularDisplacement; btVector3 _linearAcceleration; - bool _enabled; + std::atomic_bool _enabled; State _state; bool _isPushingUp; @@ -172,6 +177,8 @@ protected: btRigidBody* _rigidBody { nullptr }; uint32_t _pendingFlags { 0 }; uint32_t _previousFlags { 0 }; + + bool _flyingAllowed { true }; }; #endif // hifi_CharacterControllerInterface_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index f0539110d3..08d207fa72 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -85,10 +85,10 @@ void EntityMotionState::updateServerPhysicsVariables() { return; } - _serverPosition = _entity->getPosition(); - _serverRotation = _entity->getRotation(); - _serverVelocity = _entity->getVelocity(); - _serverAngularVelocity = _entity->getAngularVelocity(); + Transform localTransform; + _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); + _serverPosition = localTransform.getTranslation(); + _serverRotation = localTransform.getRotation(); _serverAcceleration = _entity->getAcceleration(); _serverActionData = _entity->getActionData(); } @@ -135,7 +135,14 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { _nextOwnershipBid = 0; } if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { - _body->activate(); + if (_body->isKinematicObject()) { + // only force activate kinematic bodies (dynamic shouldn't need force and + // active static bodies are special (see PhysicsEngine::_activeStaticBodies)) + _body->activate(true); + _lastKinematicStep = ObjectMotionState::getWorldSimulationStep(); + } else { + _body->activate(); + } } } @@ -152,6 +159,11 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { } assert(entityTreeIsLocked()); + if (_entity->getShapeType() == SHAPE_TYPE_STATIC_MESH + || (_body && _body->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)) { + return MOTION_TYPE_STATIC; + } + if (_entity->getDynamic()) { if (!_entity->getParentID().isNull()) { // if something would have been dynamic but is a child of something else, force it to be kinematic, instead. @@ -271,14 +283,25 @@ bool EntityMotionState::isCandidateForOwnership() const { bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // NOTE: we only get here if we think we own the simulation assert(_body); + + bool parentTransformSuccess; + Transform localToWorld = _entity->getParentTransform(parentTransformSuccess); + Transform worldToLocal; + Transform worldVelocityToLocal; + if (parentTransformSuccess) { + localToWorld.evalInverse(worldToLocal); + worldVelocityToLocal = worldToLocal; + worldVelocityToLocal.setTranslation(glm::vec3(0.0f)); + } + // if we've never checked before, our _lastStep will be 0, and we need to initialize our state if (_lastStep == 0) { btTransform xform = _body->getWorldTransform(); - _serverPosition = bulletToGLM(xform.getOrigin()); - _serverRotation = bulletToGLM(xform.getRotation()); - _serverVelocity = getBodyLinearVelocityGTSigma(); + _serverPosition = worldToLocal.transform(bulletToGLM(xform.getOrigin())); + _serverRotation = worldToLocal.getRotation() * bulletToGLM(xform.getRotation()); + _serverVelocity = worldVelocityToLocal.transform(getBodyLinearVelocityGTSigma()); _serverAcceleration = Vectors::ZERO; - _serverAngularVelocity = bulletToGLM(_body->getAngularVelocity()); + _serverAngularVelocity = worldVelocityToLocal.transform(bulletToGLM(_body->getAngularVelocity())); _lastStep = simulationStep; _serverActionData = _entity->getActionData(); _numInactiveUpdates = 1; @@ -315,11 +338,21 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _lastStep = simulationStep; if (glm::length2(_serverVelocity) > 0.0f) { - _serverVelocity += _serverAcceleration * dt; - _serverVelocity *= powf(1.0f - _body->getLinearDamping(), dt); - // NOTE: we ignore the second-order acceleration term when integrating - // the position forward because Bullet also does this. - _serverPosition += dt * _serverVelocity; + // the entity-server doesn't know where avatars are, so it doesn't do simple extrapolation for children of + // avatars. We are trying to guess what values the entity server has, so we don't do it here, either. See + // related code in EntitySimulation::moveSimpleKinematics. + bool ancestryIsKnown; + _entity->getMaximumAACube(ancestryIsKnown); + bool hasAvatarAncestor = _entity->hasAncestorOfType(NestableType::Avatar); + + if (ancestryIsKnown && !hasAvatarAncestor) { + _serverVelocity += _serverAcceleration * dt; + _serverVelocity *= powf(1.0f - _body->getLinearDamping(), dt); + + // NOTE: we ignore the second-order acceleration term when integrating + // the position forward because Bullet also does this. + _serverPosition += dt * _serverVelocity; + } } if (_entity->actionDataNeedsTransmit()) { @@ -341,7 +374,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // compute position error btTransform worldTrans = _body->getWorldTransform(); - glm::vec3 position = bulletToGLM(worldTrans.getOrigin()); + glm::vec3 position = worldToLocal.transform(bulletToGLM(worldTrans.getOrigin())); float dx2 = glm::distance2(position, _serverPosition); const float MAX_POSITION_ERROR_SQUARED = 0.000004f; // corresponds to 2mm @@ -376,7 +409,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { } } const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation - glm::quat actualRotation = bulletToGLM(worldTrans.getRotation()); + glm::quat actualRotation = worldToLocal.getRotation() * bulletToGLM(worldTrans.getRotation()); #ifdef WANT_DEBUG if ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) { @@ -404,6 +437,11 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { assert(_body); assert(entityTreeIsLocked()); + if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { + // don't send updates for someone else's avatarEntities + return false; + } + if (_entity->actionDataNeedsTransmit()) { return true; } @@ -476,11 +514,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ } // remember properties for local server prediction - _serverPosition = _entity->getPosition(); - _serverRotation = _entity->getRotation(); - _serverVelocity = _entity->getVelocity(); + Transform localTransform; + _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); + _serverPosition = localTransform.getTranslation(); + _serverRotation = localTransform.getRotation(); _serverAcceleration = _entity->getAcceleration(); - _serverAngularVelocity = _entity->getAngularVelocity(); _serverActionData = _entity->getActionData(); EntityItemProperties properties; @@ -547,8 +585,14 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; #endif - entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, id, properties); - _entity->setLastBroadcast(usecTimestampNow()); + EntityTreeElementPointer element = _entity->getElement(); + EntityTreePointer tree = element ? element->getTree() : nullptr; + + properties.setClientOnly(_entity->getClientOnly()); + properties.setOwningAvatarID(_entity->getOwningAvatarID()); + + entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, id, properties); + _entity->setLastBroadcast(now); // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. @@ -559,8 +603,13 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); - entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties); - entityDescendant->setLastBroadcast(usecTimestampNow()); + + newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly()); + newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID()); + + entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, + descendant->getID(), newQueryCubeProperties); + entityDescendant->setLastBroadcast(now); } } }); @@ -574,7 +623,7 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() { if (_body && _entity) { dirtyFlags = _entity->getDirtyFlags(); - if (dirtyFlags | Simulation::DIRTY_SIMULATOR_ID) { + if (dirtyFlags & Simulation::DIRTY_SIMULATOR_ID) { // when SIMULATOR_ID changes we must check for reinterpretation of asymmetric collision mask // bits for the avatar groups (e.g. MY_AVATAR vs OTHER_AVATAR) uint8_t entityCollisionMask = _entity->getCollisionless() ? 0 : _entity->getCollisionMask(); @@ -587,8 +636,12 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() { // we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings int bodyFlags = _body->getCollisionFlags(); bool isMoving = _entity->isMovingRelativeToParent(); - if (((bodyFlags & btCollisionObject::CF_STATIC_OBJECT) && isMoving) || - (bodyFlags & btCollisionObject::CF_KINEMATIC_OBJECT && !isMoving)) { + + if (((bodyFlags & btCollisionObject::CF_STATIC_OBJECT) && isMoving) // || + // TODO -- there is opportunity for an optimization here, but this currently causes + // excessive re-insertion of the rigid body. + // (bodyFlags & btCollisionObject::CF_KINEMATIC_OBJECT && !isMoving) + ) { dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; } } diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index de435e80da..f915121718 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -203,35 +203,37 @@ void ObjectMotionState::handleEasyChanges(uint32_t& flags) { } } - if (flags & Simulation::DIRTY_LINEAR_VELOCITY) { - btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity()); - if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { - float delta = (newLinearVelocity - _body->getLinearVelocity()).length(); - if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) { - flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + if (_body->getCollisionShape()->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) { + if (flags & Simulation::DIRTY_LINEAR_VELOCITY) { + btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity()); + if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { + float delta = (newLinearVelocity - _body->getLinearVelocity()).length(); + if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } } - } - _body->setLinearVelocity(newLinearVelocity); + _body->setLinearVelocity(newLinearVelocity); - btVector3 newGravity = glmToBullet(getObjectGravity()); - if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { - float delta = (newGravity - _body->getGravity()).length(); - if (delta > ACTIVATION_GRAVITY_DELTA || - (delta > 0.0f && _body->getGravity().length2() == 0.0f)) { - flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + btVector3 newGravity = glmToBullet(getObjectGravity()); + if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { + float delta = (newGravity - _body->getGravity()).length(); + if (delta > ACTIVATION_GRAVITY_DELTA || + (delta > 0.0f && _body->getGravity().length2() == 0.0f)) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } } + _body->setGravity(newGravity); } - _body->setGravity(newGravity); - } - if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) { - btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity()); - if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { - float delta = (newAngularVelocity - _body->getAngularVelocity()).length(); - if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { - flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) { + btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity()); + if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { + float delta = (newAngularVelocity - _body->getAngularVelocity()).length(); + if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } } + _body->setAngularVelocity(newAngularVelocity); } - _body->setAngularVelocity(newAngularVelocity); } if (flags & Simulation::DIRTY_MATERIAL) { diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 3fbf8ffaf5..9714059e7c 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -217,6 +217,14 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re } else if (entity->isReadyToComputeShape()) { ShapeInfo shapeInfo; entity->computeShapeInfo(shapeInfo); + int numPoints = shapeInfo.getLargestSubshapePointCount(); + if (shapeInfo.getType() == SHAPE_TYPE_COMPOUND) { + if (numPoints > MAX_HULL_POINTS) { + qWarning() << "convex hull with" << numPoints + << "points for entity" << entity->getName() + << "at" << entity->getPosition() << " will be reduced"; + } + } btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); if (shape) { EntityMotionState* motionState = new EntityMotionState(shape, entity); @@ -225,7 +233,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re result.push_back(motionState); entityItr = _entitiesToAddToPhysics.erase(entityItr); } else { - //qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName(); + //qWarning() << "Failed to generate new shape for entity." << entity->getName(); ++entityItr; } } else { diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index de3e9cc794..944e05e571 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -10,15 +10,66 @@ // #include +#include #include // for MILLIMETERS_PER_METER #include "ShapeFactory.h" #include "BulletUtil.h" +// These are the same normalized directions used by the btShapeHull class. +// 12 points for the face centers of a duodecohedron plus another 30 points +// for the midpoints the edges, for a total of 42. +const uint32_t NUM_UNIT_SPHERE_DIRECTIONS = 42; +static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = { + btVector3(btScalar(0.000000) , btScalar(-0.000000),btScalar(-1.000000)), + btVector3(btScalar(0.723608) , btScalar(-0.525725),btScalar(-0.447219)), + btVector3(btScalar(-0.276388) , btScalar(-0.850649),btScalar(-0.447219)), + btVector3(btScalar(-0.894426) , btScalar(-0.000000),btScalar(-0.447216)), + btVector3(btScalar(-0.276388) , btScalar(0.850649),btScalar(-0.447220)), + btVector3(btScalar(0.723608) , btScalar(0.525725),btScalar(-0.447219)), + btVector3(btScalar(0.276388) , btScalar(-0.850649),btScalar(0.447220)), + btVector3(btScalar(-0.723608) , btScalar(-0.525725),btScalar(0.447219)), + btVector3(btScalar(-0.723608) , btScalar(0.525725),btScalar(0.447219)), + btVector3(btScalar(0.276388) , btScalar(0.850649),btScalar(0.447219)), + btVector3(btScalar(0.894426) , btScalar(0.000000),btScalar(0.447216)), + btVector3(btScalar(-0.000000) , btScalar(0.000000),btScalar(1.000000)), + btVector3(btScalar(0.425323) , btScalar(-0.309011),btScalar(-0.850654)), + btVector3(btScalar(-0.162456) , btScalar(-0.499995),btScalar(-0.850654)), + btVector3(btScalar(0.262869) , btScalar(-0.809012),btScalar(-0.525738)), + btVector3(btScalar(0.425323) , btScalar(0.309011),btScalar(-0.850654)), + btVector3(btScalar(0.850648) , btScalar(-0.000000),btScalar(-0.525736)), + btVector3(btScalar(-0.525730) , btScalar(-0.000000),btScalar(-0.850652)), + btVector3(btScalar(-0.688190) , btScalar(-0.499997),btScalar(-0.525736)), + btVector3(btScalar(-0.162456) , btScalar(0.499995),btScalar(-0.850654)), + btVector3(btScalar(-0.688190) , btScalar(0.499997),btScalar(-0.525736)), + btVector3(btScalar(0.262869) , btScalar(0.809012),btScalar(-0.525738)), + btVector3(btScalar(0.951058) , btScalar(0.309013),btScalar(0.000000)), + btVector3(btScalar(0.951058) , btScalar(-0.309013),btScalar(0.000000)), + btVector3(btScalar(0.587786) , btScalar(-0.809017),btScalar(0.000000)), + btVector3(btScalar(0.000000) , btScalar(-1.000000),btScalar(0.000000)), + btVector3(btScalar(-0.587786) , btScalar(-0.809017),btScalar(0.000000)), + btVector3(btScalar(-0.951058) , btScalar(-0.309013),btScalar(-0.000000)), + btVector3(btScalar(-0.951058) , btScalar(0.309013),btScalar(-0.000000)), + btVector3(btScalar(-0.587786) , btScalar(0.809017),btScalar(-0.000000)), + btVector3(btScalar(-0.000000) , btScalar(1.000000),btScalar(-0.000000)), + btVector3(btScalar(0.587786) , btScalar(0.809017),btScalar(-0.000000)), + btVector3(btScalar(0.688190) , btScalar(-0.499997),btScalar(0.525736)), + btVector3(btScalar(-0.262869) , btScalar(-0.809012),btScalar(0.525738)), + btVector3(btScalar(-0.850648) , btScalar(0.000000),btScalar(0.525736)), + btVector3(btScalar(-0.262869) , btScalar(0.809012),btScalar(0.525738)), + btVector3(btScalar(0.688190) , btScalar(0.499997),btScalar(0.525736)), + btVector3(btScalar(0.525730) , btScalar(0.000000),btScalar(0.850652)), + btVector3(btScalar(0.162456) , btScalar(-0.499995),btScalar(0.850654)), + btVector3(btScalar(-0.425323) , btScalar(-0.309011),btScalar(0.850654)), + btVector3(btScalar(-0.425323) , btScalar(0.309011),btScalar(0.850654)), + btVector3(btScalar(0.162456) , btScalar(0.499995),btScalar(0.850654)) +}; -btConvexHullShape* ShapeFactory::createConvexHull(const QVector& points) { +// util method +btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) { + //std::cout << "adebug createConvexHull() points.size() = " << points.size() << std::endl; // adebug assert(points.size() > 0); btConvexHullShape* hull = new btConvexHullShape(); @@ -64,13 +115,133 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector& poin correctedPoint = (points[i] - center) * relativeScale + center; hull->addPoint(btVector3(correctedPoint[0], correctedPoint[1], correctedPoint[2]), false); } + + uint32_t numPoints = (uint32_t)hull->getNumPoints(); + if (numPoints > MAX_HULL_POINTS) { + // we have too many points, so we compute point projections along canonical unit vectors + // and keep the those that project the farthest + btVector3 btCenter = glmToBullet(center); + btVector3* shapePoints = hull->getUnscaledPoints(); + std::vector finalIndices; + finalIndices.reserve(NUM_UNIT_SPHERE_DIRECTIONS); + for (uint32_t i = 0; i < NUM_UNIT_SPHERE_DIRECTIONS; ++i) { + uint32_t bestIndex = 0; + btScalar maxDistance = _unitSphereDirections[i].dot(shapePoints[0] - btCenter); + for (uint32_t j = 1; j < numPoints; ++j) { + btScalar distance = _unitSphereDirections[i].dot(shapePoints[j] - btCenter); + if (distance > maxDistance) { + maxDistance = distance; + bestIndex = j; + } + } + bool keep = true; + for (uint32_t j = 0; j < finalIndices.size(); ++j) { + if (finalIndices[j] == bestIndex) { + keep = false; + break; + } + } + if (keep) { + finalIndices.push_back(bestIndex); + } + } + + // we cannot copy Bullet shapes so we must create a new one... + btConvexHullShape* newHull = new btConvexHullShape(); + for (uint32_t i = 0; i < finalIndices.size(); ++i) { + newHull->addPoint(shapePoints[finalIndices[i]], false); + } + // ...and delete the old one + delete hull; + hull = newHull; + } + hull->recalcLocalAabb(); return hull; } +// util method +btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { + assert(info.getType() == SHAPE_TYPE_STATIC_MESH); // should only get here for mesh shapes + + const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); + assert(pointCollection.size() == 1); // should only have one mesh + + const ShapeInfo::PointList& pointList = pointCollection[0]; + assert(pointList.size() > 2); // should have at least one triangle's worth of points + + const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); + assert(triangleIndices.size() > 2); // should have at least one triangle's worth of indices + + // allocate mesh buffers + btIndexedMesh mesh; + int32_t numIndices = triangleIndices.size(); + const int32_t VERTICES_PER_TRIANGLE = 3; + mesh.m_numTriangles = numIndices / VERTICES_PER_TRIANGLE; + if (numIndices < INT16_MAX) { + // small number of points so we can use 16-bit indices + mesh.m_triangleIndexBase = new unsigned char[sizeof(int16_t) * (size_t)numIndices]; + mesh.m_indexType = PHY_SHORT; + mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int16_t); + } else { + mesh.m_triangleIndexBase = new unsigned char[sizeof(int32_t) * (size_t)numIndices]; + mesh.m_indexType = PHY_INTEGER; + mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int32_t); + } + mesh.m_numVertices = pointList.size(); + mesh.m_vertexBase = new unsigned char[VERTICES_PER_TRIANGLE * sizeof(btScalar) * (size_t)mesh.m_numVertices]; + mesh.m_vertexStride = VERTICES_PER_TRIANGLE * sizeof(btScalar); + mesh.m_vertexType = PHY_FLOAT; + + // copy data into buffers + btScalar* vertexData = static_cast((void*)(mesh.m_vertexBase)); + for (int32_t i = 0; i < mesh.m_numVertices; ++i) { + int32_t j = i * VERTICES_PER_TRIANGLE; + const glm::vec3& point = pointList[i]; + vertexData[j] = point.x; + vertexData[j + 1] = point.y; + vertexData[j + 2] = point.z; + } + if (numIndices < INT16_MAX) { + int16_t* indices = static_cast((void*)(mesh.m_triangleIndexBase)); + for (int32_t i = 0; i < numIndices; ++i) { + indices[i] = (int16_t)triangleIndices[i]; + } + } else { + int32_t* indices = static_cast((void*)(mesh.m_triangleIndexBase)); + for (int32_t i = 0; i < numIndices; ++i) { + indices[i] = triangleIndices[i]; + } + } + + // store buffers in a new dataArray and return the pointer + // (external StaticMeshShape will own all of the data that was allocated here) + btTriangleIndexVertexArray* dataArray = new btTriangleIndexVertexArray; + dataArray->addIndexedMesh(mesh, mesh.m_indexType); + return dataArray; +} + +// util method +void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) { + assert(dataArray); + IndexedMeshArray& meshes = dataArray->getIndexedMeshArray(); + for (int32_t i = 0; i < meshes.size(); ++i) { + btIndexedMesh mesh = meshes[i]; + mesh.m_numTriangles = 0; + delete [] mesh.m_triangleIndexBase; + mesh.m_triangleIndexBase = nullptr; + mesh.m_numVertices = 0; + delete [] mesh.m_vertexBase; + mesh.m_vertexBase = nullptr; + } + meshes.clear(); + delete dataArray; +} + btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { btCollisionShape* shape = NULL; int type = info.getType(); + //std::cout << "adebug createShapeFromInfo() type = " << type << std::endl; // adebug switch(type) { case SHAPE_TYPE_BOX: { shape = new btBoxShape(glmToBullet(info.getHalfExtents())); @@ -88,23 +259,80 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { shape = new btCapsuleShape(radius, height); } break; - case SHAPE_TYPE_COMPOUND: { - const QVector>& points = info.getPoints(); + case SHAPE_TYPE_COMPOUND: + case SHAPE_TYPE_SIMPLE_HULL: { + const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); uint32_t numSubShapes = info.getNumSubShapes(); if (numSubShapes == 1) { - shape = createConvexHull(info.getPoints()[0]); + shape = createConvexHull(pointCollection[0]); } else { auto compound = new btCompoundShape(); btTransform trans; trans.setIdentity(); - foreach (QVector hullPoints, points) { + foreach (const ShapeInfo::PointList& hullPoints, pointCollection) { btConvexHullShape* hull = createConvexHull(hullPoints); - compound->addChildShape (trans, hull); + compound->addChildShape(trans, hull); } shape = compound; } } break; + case SHAPE_TYPE_SIMPLE_COMPOUND: { + const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); + const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); + uint32_t numIndices = triangleIndices.size(); + uint32_t numMeshes = info.getNumSubShapes(); + const uint32_t MIN_NUM_SIMPLE_COMPOUND_INDICES = 2; // END_OF_MESH_PART + END_OF_MESH + if (numMeshes > 0 && numIndices > MIN_NUM_SIMPLE_COMPOUND_INDICES) { + uint32_t i = 0; + std::vector hulls; + for (auto& points : pointCollection) { + // build a hull around each part + while (i < numIndices) { + ShapeInfo::PointList hullPoints; + hullPoints.reserve(points.size()); + while (i < numIndices) { + int32_t j = triangleIndices[i]; + ++i; + if (j == END_OF_MESH_PART) { + // end of part + break; + } + hullPoints.push_back(points[j]); + } + if (hullPoints.size() > 0) { + btConvexHullShape* hull = createConvexHull(hullPoints); + hulls.push_back(hull); + } + + assert(i < numIndices); + if (triangleIndices[i] == END_OF_MESH) { + // end of mesh + ++i; + break; + } + } + } + uint32_t numHulls = (uint32_t)hulls.size(); + if (numHulls == 1) { + shape = hulls[0]; + } else { + auto compound = new btCompoundShape(); + btTransform trans; + trans.setIdentity(); + for (auto hull : hulls) { + compound->addChildShape(trans, hull); + } + shape = compound; + } + } + } + break; + case SHAPE_TYPE_STATIC_MESH: { + btTriangleIndexVertexArray* dataArray = createStaticMeshArray(info); + shape = new StaticMeshShape(dataArray); + } + break; } if (shape) { if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) { @@ -138,3 +366,14 @@ void ShapeFactory::deleteShape(btCollisionShape* shape) { } delete shape; } + +// the dataArray must be created before we create the StaticMeshShape +ShapeFactory::StaticMeshShape::StaticMeshShape(btTriangleIndexVertexArray* dataArray) +: btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) { + assert(dataArray); +} + +ShapeFactory::StaticMeshShape::~StaticMeshShape() { + deleteStaticMeshArray(_dataArray); + _dataArray = nullptr; +} diff --git a/libraries/physics/src/ShapeFactory.h b/libraries/physics/src/ShapeFactory.h index 1ba2bdb619..6202612eb9 100644 --- a/libraries/physics/src/ShapeFactory.h +++ b/libraries/physics/src/ShapeFactory.h @@ -20,9 +20,22 @@ // translates between ShapeInfo and btShape namespace ShapeFactory { - btConvexHullShape* createConvexHull(const QVector& points); btCollisionShape* createShapeFromInfo(const ShapeInfo& info); void deleteShape(btCollisionShape* shape); + + //btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info); + //void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray); + + class StaticMeshShape : public btBvhTriangleMeshShape { + public: + StaticMeshShape() = delete; + StaticMeshShape(btTriangleIndexVertexArray* dataArray); + ~StaticMeshShape(); + + private: + // the StaticMeshShape owns its vertex/index data + btTriangleIndexVertexArray* _dataArray; + }; }; #endif // hifi_ShapeFactory_h diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index 4231d1eb60..4fa660239c 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -32,15 +32,13 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { if (info.getType() == SHAPE_TYPE_NONE) { return NULL; } - if (info.getType() != SHAPE_TYPE_COMPOUND) { - // Very small or large non-compound objects are not supported. - float diagonal = 4.0f * glm::length2(info.getHalfExtents()); - const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube - if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED) { - // qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal; - return NULL; - } + const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube + if (4.0f * glm::length2(info.getHalfExtents()) < MIN_SHAPE_DIAGONAL_SQUARED) { + // tiny shapes are not supported + // qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal; + return NULL; } + DoubleHashKey key = info.getHash(); ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { @@ -66,8 +64,8 @@ bool ShapeManager::releaseShapeByKey(const DoubleHashKey& key) { shapeRef->refCount--; if (shapeRef->refCount == 0) { _pendingGarbage.push_back(key); - const int MAX_GARBAGE_CAPACITY = 255; - if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) { + const int MAX_SHAPE_GARBAGE_CAPACITY = 255; + if (_pendingGarbage.size() > MAX_SHAPE_GARBAGE_CAPACITY) { collectGarbage(); } } diff --git a/libraries/plugins/src/plugins/CodecPlugin.h b/libraries/plugins/src/plugins/CodecPlugin.h new file mode 100644 index 0000000000..404f05e860 --- /dev/null +++ b/libraries/plugins/src/plugins/CodecPlugin.h @@ -0,0 +1,36 @@ +// +// CodecPlugin.h +// plugins/src/plugins +// +// Created by Brad Hefta-Gaub on 6/9/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "Plugin.h" + +class Encoder { +public: + virtual ~Encoder() { } + virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) = 0; +}; + +class Decoder { +public: + virtual ~Decoder() { } + virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) = 0; + + // numFrames - number of samples (mono) or sample-pairs (stereo) + virtual void trackLostFrames(int numFrames) = 0; +}; + +class CodecPlugin : public Plugin { +public: + virtual Encoder* createEncoder(int sampleRate, int numChannels) = 0; + virtual Decoder* createDecoder(int sampleRate, int numChannels) = 0; + virtual void releaseEncoder(Encoder* encoder) = 0; + virtual void releaseDecoder(Decoder* decoder) = 0; +}; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index a217041f4e..747c72c08e 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -1,28 +1,6 @@ #include "DisplayPlugin.h" #include -#include - -#include "PluginContainer.h" - -bool DisplayPlugin::activate() { - if (isHmd() && (getHmdScreen() >= 0)) { - _container->showDisplayPluginsTools(); - } - return Parent::activate(); -} - -void DisplayPlugin::deactivate() { - _container->showDisplayPluginsTools(false); - if (!_container->currentDisplayActions().isEmpty()) { - auto menu = _container->getPrimaryMenu(); - foreach(auto itemInfo, _container->currentDisplayActions()) { - menu->removeMenuItem(itemInfo.first, itemInfo.second); - } - _container->currentDisplayActions().clear(); - } - Parent::deactivate(); -} int64_t DisplayPlugin::getPaintDelayUsecs() const { std::lock_guard lock(_paintDelayMutex); diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 41f380aa86..f0ba762ecb 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -16,16 +16,18 @@ #include #include #include -class QImage; #include #include +#include #include "Plugin.h" +class QImage; + enum Eye { - Left, - Right + Left = (int)bilateral::Side::Left, + Right = (int)bilateral::Side::Right }; /* @@ -56,7 +58,73 @@ namespace gpu { using TexturePointer = std::shared_ptr; } -class DisplayPlugin : public Plugin { +// Stereo display functionality +// TODO move out of this file don't derive DisplayPlugin from this. Instead use dynamic casting when +// displayPlugin->isStereo returns true +class StereoDisplay { +public: + // Stereo specific methods + virtual glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { + return baseProjection; + } + + virtual glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const { + return baseProjection; + } + + virtual float getIPD() const { return AVERAGE_HUMAN_IPD; } +}; + +// HMD display functionality +// TODO move out of this file don't derive DisplayPlugin from this. Instead use dynamic casting when +// displayPlugin->isHmd returns true +class HmdDisplay : public StereoDisplay { +public: + // HMD specific methods + // TODO move these into another class? + virtual glm::mat4 getEyeToHeadTransform(Eye eye) const { + static const glm::mat4 transform; return transform; + } + + // returns a copy of the most recent head pose, computed via updateHeadPose + virtual glm::mat4 getHeadPose() const { + return glm::mat4(); + } + + // Needed for timewarp style features + virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { + // NOOP + } + + virtual void abandonCalibration() {} + + virtual void resetSensors() {} + + enum Hand { + LeftHand = 0x01, + RightHand = 0x02, + }; + + enum class HandLaserMode { + None, // Render no hand lasers + Overlay, // Render hand lasers only if they intersect with the UI layer, and stop at the UI layer + }; + + virtual bool setHandLaser( + uint32_t hands, // Bits from the Hand enum + HandLaserMode mode, // Mode in which to render + const vec4& color = vec4(1), // The color of the rendered laser + const vec3& direction = vec3(0, 0, -1) // The direction in which to render the hand lasers + ) { + return false; + } + + virtual bool suppressKeyboard() { return false; } + virtual void unsuppressKeyboard() {}; + virtual bool isKeyboardVisible() { return false; } +}; + +class DisplayPlugin : public Plugin, public HmdDisplay { Q_OBJECT using Parent = Plugin; public: @@ -64,8 +132,6 @@ public: Present = QEvent::User + 1 }; - bool activate() override; - void deactivate() override; virtual bool isHmd() const { return false; } virtual int getHmdScreen() const { return -1; } /// By default, all HMDs are stereo @@ -117,42 +183,12 @@ public: return QRect(0, 0, recommendedSize.x, recommendedSize.y); } - // Stereo specific methods - virtual glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { - return baseProjection; - } - - virtual glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const { - 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; - } - // will query the underlying hmd api to compute the most recent head pose - virtual void beginFrameRender(uint32_t frameIndex) {} + virtual bool beginFrameRender(uint32_t frameIndex) { return true; } - // returns a copy of the most recent head pose, computed via updateHeadPose - virtual glm::mat4 getHeadPose() const { - return glm::mat4(); - } - - // Needed for timewarp style features - virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { - // NOOP - } - - virtual float getIPD() const { return AVERAGE_HUMAN_IPD; } - - virtual void abandonCalibration() {} - virtual void resetSensors() {} virtual float devicePixelRatio() { return 1.0f; } // Rate at which we present to the display device virtual float presentRate() const { return -1.0f; } @@ -160,6 +196,7 @@ public: virtual float newFramePresentRate() const { return -1.0f; } // Rate at which rendered frames are being skipped virtual float droppedFrameRate() const { return -1.0f; } + uint32_t presentCount() const { return _presentedFrameIndex; } // Time since last call to incrementPresentCount (only valid if DEBUG_PAINT_DELAY is defined) int64_t getPaintDelayUsecs() const; @@ -168,6 +205,7 @@ public: static const QString& MENU_PATH(); + signals: void recommendedFramebufferSizeChanged(const QSize & size); diff --git a/libraries/plugins/src/plugins/Forward.h b/libraries/plugins/src/plugins/Forward.h index 036b42f7d7..723c4f321e 100644 --- a/libraries/plugins/src/plugins/Forward.h +++ b/libraries/plugins/src/plugins/Forward.h @@ -13,10 +13,12 @@ enum class PluginType { DISPLAY_PLUGIN, INPUT_PLUGIN, + CODEC_PLUGIN, }; class DisplayPlugin; class InputPlugin; +class CodecPlugin; class Plugin; class PluginContainer; class PluginManager; @@ -25,4 +27,6 @@ using DisplayPluginPointer = std::shared_ptr; using DisplayPluginList = std::vector; using InputPluginPointer = std::shared_ptr; using InputPluginList = std::vector; +using CodecPluginPointer = std::shared_ptr; +using CodecPluginList = std::vector; diff --git a/libraries/plugins/src/plugins/InputPlugin.h b/libraries/plugins/src/plugins/InputPlugin.h index b45fa862c1..02ae5f58d5 100644 --- a/libraries/plugins/src/plugins/InputPlugin.h +++ b/libraries/plugins/src/plugins/InputPlugin.h @@ -18,10 +18,8 @@ namespace controller { class InputPlugin : public Plugin { public: - virtual bool isJointController() const = 0; - virtual void pluginFocusOutEvent() = 0; - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) = 0; + virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) = 0; }; diff --git a/libraries/plugins/src/plugins/Plugin.h b/libraries/plugins/src/plugins/Plugin.h index fb5bf0ba55..0452c7fbfe 100644 --- a/libraries/plugins/src/plugins/Plugin.h +++ b/libraries/plugins/src/plugins/Plugin.h @@ -15,6 +15,7 @@ #include "Forward.h" class Plugin : public QObject { + Q_OBJECT public: /// \return human-readable name virtual const QString& getName() const = 0; @@ -63,6 +64,13 @@ public: virtual void saveSettings() const {} virtual void loadSettings() {} +signals: + // These signals should be emitted when a device is first known to be available. In some cases this will + // be in `init()`, in other cases, like Neuron, this isn't known until activation. + // SDL2 isn't a device itself, but can have 0+ subdevices. subdeviceConnected is used in this case. + void deviceConnected(QString pluginName) const; + void subdeviceConnected(QString pluginName, QString subdeviceName) const; + protected: bool _active { false }; PluginContainer* _container { nullptr }; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 936cb8a7ee..0b4afe1be0 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -14,10 +14,13 @@ #include #include +#include +#include + #include "RuntimePlugin.h" +#include "CodecPlugin.h" #include "DisplayPlugin.h" #include "InputPlugin.h" -#include "PluginContainer.h" PluginManager* PluginManager::getInstance() { @@ -25,6 +28,50 @@ PluginManager* PluginManager::getInstance() { return &_manager; } +QString getPluginNameFromMetaData(QJsonObject object) { + static const char* METADATA_KEY = "MetaData"; + static const char* NAME_KEY = "name"; + + if (!object.contains(METADATA_KEY) || !object[METADATA_KEY].isObject()) { + return QString(); + } + + auto metaDataObject = object[METADATA_KEY].toObject(); + + if (!metaDataObject.contains(NAME_KEY) || !metaDataObject[NAME_KEY].isString()) { + return QString(); + } + + return metaDataObject[NAME_KEY].toString(); +} + +QString getPluginIIDFromMetaData(QJsonObject object) { + static const char* IID_KEY = "IID"; + + if (!object.contains(IID_KEY) || !object[IID_KEY].isString()) { + return QString(); + } + + return object[IID_KEY].toString(); +} + +QStringList preferredDisplayPlugins; +QStringList disabledDisplays; +QStringList disabledInputs; + +bool isDisabled(QJsonObject metaData) { + auto name = getPluginNameFromMetaData(metaData); + auto iid = getPluginIIDFromMetaData(metaData); + + if (iid == DisplayProvider_iid) { + return disabledDisplays.contains(name); + } else if (iid == InputProvider_iid) { + return disabledInputs.contains(name); + } + + return false; +} + using Loader = QSharedPointer; using LoaderList = QList; @@ -43,11 +90,21 @@ const LoaderList& getLoadedPlugins() { qDebug() << "Loading runtime plugins from " << pluginPath; auto candidates = pluginDir.entryList(); for (auto plugin : candidates) { - qDebug() << "Attempting plugins " << plugin; + qDebug() << "Attempting plugin" << qPrintable(plugin); QSharedPointer loader(new QPluginLoader(pluginPath + plugin)); + + if (isDisabled(loader->metaData())) { + qWarning() << "Plugin" << qPrintable(plugin) << "is disabled"; + // Skip this one, it's disabled + continue; + } + if (loader->load()) { - qDebug() << "Plugins " << plugin << " success"; + qDebug() << "Plugin" << qPrintable(plugin) << "loaded successfully"; loadedPlugins.push_back(loader); + } else { + qDebug() << "Plugin" << qPrintable(plugin) << "failed to load:"; + qDebug() << " " << qPrintable(loader->errorString()); } } } @@ -61,11 +118,20 @@ PluginManager::PluginManager() { // TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class extern DisplayPluginList getDisplayPlugins(); extern InputPluginList getInputPlugins(); +extern CodecPluginList getCodecPlugins(); extern void saveInputPluginSettings(const InputPluginList& plugins); +static DisplayPluginList displayPlugins; const DisplayPluginList& PluginManager::getDisplayPlugins() { - static DisplayPluginList displayPlugins; static std::once_flag once; + static auto deviceAddedCallback = [](QString deviceName) { + qDebug() << "Added device: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("display", deviceName); + }; + static auto subdeviceAddedCallback = [](QString pluginName, QString deviceName) { + qDebug() << "Added subdevice: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("display", pluginName + " | " + deviceName); + }; std::call_once(once, [&] { // Grab the built in plugins @@ -80,9 +146,10 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { } } } - auto& container = PluginContainer::getInstance(); for (auto plugin : displayPlugins) { - plugin->setContainer(&container); + connect(plugin.get(), &Plugin::deviceConnected, this, deviceAddedCallback, Qt::QueuedConnection); + connect(plugin.get(), &Plugin::subdeviceConnected, this, subdeviceAddedCallback, Qt::QueuedConnection); + plugin->setContainer(_container); plugin->init(); } @@ -90,9 +157,28 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { return displayPlugins; } +void PluginManager::disableDisplayPlugin(const QString& name) { + for (size_t i = 0; i < displayPlugins.size(); ++i) { + if (displayPlugins[i]->getName() == name) { + displayPlugins.erase(displayPlugins.begin() + i); + break; + } + } +} + + const InputPluginList& PluginManager::getInputPlugins() { static InputPluginList inputPlugins; static std::once_flag once; + static auto deviceAddedCallback = [](QString deviceName) { + qDebug() << "Added device: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("input", deviceName); + }; + static auto subdeviceAddedCallback = [](QString pluginName, QString deviceName) { + qDebug() << "Added subdevice: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("input", pluginName + " | " + deviceName); + }; + std::call_once(once, [&] { inputPlugins = ::getInputPlugins(); @@ -101,20 +187,86 @@ const InputPluginList& PluginManager::getInputPlugins() { InputProvider* inputProvider = qobject_cast(loader->instance()); if (inputProvider) { for (auto inputPlugin : inputProvider->getInputPlugins()) { - inputPlugins.push_back(inputPlugin); + if (inputPlugin->isSupported()) { + inputPlugins.push_back(inputPlugin); + } } } } - auto& container = PluginContainer::getInstance(); for (auto plugin : inputPlugins) { - plugin->setContainer(&container); + connect(plugin.get(), &Plugin::deviceConnected, this, deviceAddedCallback, Qt::QueuedConnection); + connect(plugin.get(), &Plugin::subdeviceConnected, this, subdeviceAddedCallback, Qt::QueuedConnection); + plugin->setContainer(_container); plugin->init(); } }); return inputPlugins; } +const CodecPluginList& PluginManager::getCodecPlugins() { + static CodecPluginList codecPlugins; + static std::once_flag once; + std::call_once(once, [&] { + //codecPlugins = ::getCodecPlugins(); + + // Now grab the dynamic plugins + for (auto loader : getLoadedPlugins()) { + CodecProvider* codecProvider = qobject_cast(loader->instance()); + if (codecProvider) { + for (auto codecPlugin : codecProvider->getCodecPlugins()) { + if (codecPlugin->isSupported()) { + codecPlugins.push_back(codecPlugin); + } + } + } + } + + for (auto plugin : codecPlugins) { + plugin->setContainer(_container); + plugin->init(); + + qDebug() << "init codec:" << plugin->getName(); + } + }); + return codecPlugins; +} + + +void PluginManager::setPreferredDisplayPlugins(const QStringList& displays) { + preferredDisplayPlugins = displays; +} + +DisplayPluginList PluginManager::getPreferredDisplayPlugins() { + static DisplayPluginList displayPlugins; + + static std::once_flag once; + std::call_once(once, [&] { + // Grab the built in plugins + auto plugins = getDisplayPlugins(); + + for (auto pluginName : preferredDisplayPlugins) { + auto it = std::find_if(plugins.begin(), plugins.end(), [&](DisplayPluginPointer plugin) { + return plugin->getName() == pluginName; + }); + if (it != plugins.end()) { + displayPlugins.push_back(*it); + } + } + }); + + return displayPlugins; +} + + +void PluginManager::disableDisplays(const QStringList& displays) { + disabledDisplays << displays; +} + +void PluginManager::disableInputs(const QStringList& inputs) { + disabledInputs << inputs; +} + void PluginManager::saveSettings() { saveInputPluginSettings(getInputPlugins()); } diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index 2e056414ec..30d52298da 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -13,10 +13,21 @@ class PluginManager : public QObject { public: - static PluginManager* getInstance(); - PluginManager(); + static PluginManager* getInstance(); + PluginManager(); - const DisplayPluginList& getDisplayPlugins(); - const InputPluginList& getInputPlugins(); - void saveSettings(); + const DisplayPluginList& getDisplayPlugins(); + const InputPluginList& getInputPlugins(); + const CodecPluginList& getCodecPlugins(); + + DisplayPluginList getPreferredDisplayPlugins(); + void setPreferredDisplayPlugins(const QStringList& displays); + + void disableDisplayPlugin(const QString& name); + void disableDisplays(const QStringList& displays); + void disableInputs(const QStringList& inputs); + void saveSettings(); + void setContainer(PluginContainer* container) { _container = container; } +private: + PluginContainer* _container { nullptr }; }; diff --git a/libraries/plugins/src/plugins/RuntimePlugin.h b/libraries/plugins/src/plugins/RuntimePlugin.h index d7bf31ea28..9bf15f344d 100644 --- a/libraries/plugins/src/plugins/RuntimePlugin.h +++ b/libraries/plugins/src/plugins/RuntimePlugin.h @@ -34,3 +34,12 @@ public: #define InputProvider_iid "com.highfidelity.plugins.input" Q_DECLARE_INTERFACE(InputProvider, InputProvider_iid) +class CodecProvider { +public: + virtual ~CodecProvider() {} + virtual CodecPluginList getCodecPlugins() = 0; +}; + +#define CodecProvider_iid "com.highfidelity.plugins.codec" +Q_DECLARE_INTERFACE(CodecProvider, CodecProvider_iid) + diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index cedf76b37a..7dd729384c 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -19,7 +19,7 @@ #include #include -#include "ProceduralShaders.h" +#include "ProceduralCommon_frag.h" // Userdata parsing constants static const QString PROCEDURAL_USER_DATA_KEY = "ProceduralEntity"; @@ -39,6 +39,7 @@ static const std::string STANDARD_UNIFORM_NAMES[Procedural::NUM_STANDARD_UNIFORM "iFrameCount", "iWorldScale", "iWorldPosition", + "iWorldOrientation", "iChannelResolution" }; @@ -95,15 +96,13 @@ bool Procedural::parseVersion(const QJsonValue& version) { bool Procedural::parseUrl(const QUrl& shaderUrl) { if (!shaderUrl.isValid()) { - qWarning() << "Invalid shader URL: " << shaderUrl; + if (!shaderUrl.isEmpty()) { + qWarning() << "Invalid shader URL: " << shaderUrl; + } _networkShader.reset(); return false; } - if (_shaderUrl == shaderUrl) { - return true; - } - _shaderUrl = shaderUrl; _shaderDirty = true; @@ -206,9 +205,10 @@ bool Procedural::ready() { return true; } -void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size) { +void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation) { _entityDimensions = size; _entityPosition = position; + _entityOrientation = glm::mat3_cast(orientation); if (_shaderUrl.isLocalFile()) { auto lastModified = (quint64)QFileInfo(_shaderPath).lastModified().toMSecsSinceEpoch(); if (lastModified > _shaderModified) { @@ -231,7 +231,7 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm std::string fragmentShaderSource = _fragmentSource; size_t replaceIndex = fragmentShaderSource.find(PROCEDURAL_COMMON_BLOCK); if (replaceIndex != std::string::npos) { - fragmentShaderSource.replace(replaceIndex, PROCEDURAL_COMMON_BLOCK.size(), SHADER_COMMON); + fragmentShaderSource.replace(replaceIndex, PROCEDURAL_COMMON_BLOCK.size(), ProceduralCommon_frag); } replaceIndex = fragmentShaderSource.find(PROCEDURAL_VERSION); @@ -408,10 +408,10 @@ void Procedural::setupUniforms() { }); } - if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[SCALE]) { + if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[ORIENTATION]) { // FIXME move into the 'set once' section, since this doesn't change over time _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform(_standardUniformSlots[SCALE], _entityDimensions); + batch._glUniform(_standardUniformSlots[ORIENTATION], _entityOrientation); }); } diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index f928d0e594..6991b47946 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -38,7 +38,7 @@ public: void parse(const QString& userDataJson); bool ready(); - void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size); + void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation); const gpu::ShaderPointer& getShader() const { return _shader; } glm::vec4 getColor(const glm::vec4& entityColor); @@ -56,6 +56,7 @@ public: FRAME_COUNT, SCALE, POSITION, + ORIENTATION, CHANNEL_RESOLUTION, NUM_STANDARD_UNIFORMS }; @@ -93,6 +94,7 @@ protected: // Entity metadata glm::vec3 _entityDimensions; glm::vec3 _entityPosition; + glm::mat3 _entityOrientation; private: // This should only be called from the render thread, as it shares data with Procedural::prepare diff --git a/libraries/procedural/src/procedural/ProceduralShaders.h b/libraries/procedural/src/procedural/ProceduralCommon.slf similarity index 93% rename from libraries/procedural/src/procedural/ProceduralShaders.h rename to libraries/procedural/src/procedural/ProceduralCommon.slf index eddf53cb09..d4144ad537 100644 --- a/libraries/procedural/src/procedural/ProceduralShaders.h +++ b/libraries/procedural/src/procedural/ProceduralCommon.slf @@ -1,3 +1,5 @@ +<@include gpu/Config.slh@> +// Generated on <$_SCRIBE_DATE$> // // Created by Bradley Austin Davis on 2015/09/05 // Copyright 2013-2015 High Fidelity, Inc. @@ -17,11 +19,11 @@ // https://github.com/ashima/webgl-noise // - -const std::string SHADER_COMMON = R"SHADER( +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> float mod289(float x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; + return x - floor(x * (1.0 / 289.0)) * 289.0; } vec2 mod289(vec2 x) { @@ -262,10 +264,6 @@ float snoise(vec2 v) { return 130.0 * dot(m, g); } -// shader playback time (in seconds) -uniform float iGlobalTime; -// the dimensions of the object being rendered -uniform vec3 iWorldScale; #define PROCEDURAL 1 @@ -273,6 +271,11 @@ uniform vec3 iWorldScale; #ifdef PROCEDURAL_V1 +// shader playback time (in seconds) +uniform float iGlobalTime; +// the dimensions of the object being rendered +uniform vec3 iWorldScale; + #else // Unimplemented uniforms @@ -286,15 +289,16 @@ const float iSampleRate = 1.0; const vec4 iChannelTime = vec4(0.0); +uniform float iGlobalTime; // shader playback time (in seconds) uniform vec4 iDate; uniform int iFrameCount; -uniform vec3 iWorldPosition; +uniform vec3 iWorldPosition; // the position of the object being rendered +uniform vec3 iWorldScale; // the dimensions of the object being rendered +uniform mat3 iWorldOrientation; // the orientation of the object being rendered uniform vec3 iChannelResolution[4]; uniform sampler2D iChannel0; uniform sampler2D iChannel1; uniform sampler2D iChannel2; uniform sampler2D iChannel3; -#endif - -)SHADER"; +#endif \ No newline at end of file diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 1aa59c90d7..9e9a26d902 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -52,7 +52,7 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, batch.setModelTransform(Transform()); // only for Mac auto& procedural = skybox._procedural; - procedural.prepare(batch, glm::vec3(0), glm::vec3(1)); + procedural.prepare(batch, glm::vec3(0), glm::vec3(1), glm::quat()); auto textureSlot = procedural.getShader()->getTextures().findLocation("cubeMap"); auto bufferSlot = procedural.getShader()->getBuffers().findLocation("skyboxBuffer"); skybox.prepare(batch, textureSlot, bufferSlot); diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index c4f28aeffd..2a7d33e33a 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME render-utils) -AUTOSCRIBE_SHADER_LIB(gpu model render) +AUTOSCRIBE_SHADER_LIB(gpu model render procedural) # pull in the resources.qrc file qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") setup_hifi_library(Widgets OpenGL Network Qml Quick Script) diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 3b0e4c8d18..4b283731d2 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -283,6 +283,7 @@ void AmbientOcclusionEffect::updateGaussianDistribution() { } void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { +#ifdef FIX_THE_FRAMEBUFFER_CACHE assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); @@ -406,4 +407,5 @@ void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext // Update the timer std::static_pointer_cast(renderContext->jobConfig)->gpuTime = _gpuTimer.getAverage(); +#endif } diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index fc09f8431d..2f273f6202 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -32,6 +32,9 @@ Antialiasing::Antialiasing() { } const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { + int width = DependencyManager::get()->getFrameBufferSize().width(); + int height = DependencyManager::get()->getFrameBufferSize().height(); + if (!_antialiasingPipeline) { auto vs = gpu::Shader::createVertex(std::string(fxaa_vert)); auto ps = gpu::Shader::createPixel(std::string(fxaa_frag)); @@ -49,11 +52,8 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { state->setDepthTest(false, false, gpu::LESS_EQUAL); // Link the antialiasing FBO to texture - _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, - DependencyManager::get()->getFrameBufferSize().width(), DependencyManager::get()->getFrameBufferSize().height())); - auto format = DependencyManager::get()->getLightingTexture()->getTexelFormat(); - auto width = _antialiasingBuffer->getWidth(); - auto height = _antialiasingBuffer->getHeight(); + _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + auto format = gpu::Element::COLOR_SRGBA_32; // DependencyManager::get()->getLightingTexture()->getTexelFormat(); auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); _antialiasingTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); @@ -62,10 +62,8 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { _antialiasingPipeline = gpu::Pipeline::create(program, state); } - int w = DependencyManager::get()->getFrameBufferSize().width(); - int h = DependencyManager::get()->getFrameBufferSize().height(); - if (w != _antialiasingBuffer->getWidth() || h != _antialiasingBuffer->getHeight()) { - _antialiasingBuffer->resize(w, h); + if (width != _antialiasingBuffer->getWidth() || height != _antialiasingBuffer->getHeight()) { + _antialiasingBuffer->resize(width, height); } return _antialiasingPipeline; @@ -92,7 +90,7 @@ const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { return _blendPipeline; } -void Antialiasing::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { +void Antialiasing::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceBuffer) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); @@ -126,7 +124,7 @@ void Antialiasing::run(const render::SceneContextPointer& sceneContext, const re // FXAA step getAntialiasingPipeline(); - batch.setResourceTexture(0, framebufferCache->getLightingTexture()); + batch.setResourceTexture(0, sourceBuffer->getRenderBuffer(0)); batch.setFramebuffer(_antialiasingBuffer); batch.setPipeline(getAntialiasingPipeline()); @@ -152,10 +150,11 @@ void Antialiasing::run(const render::SceneContextPointer& sceneContext, const re glm::vec2 texCoordBottomRight(1.0f, 1.0f); DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); + // Blend step getBlendPipeline(); batch.setResourceTexture(0, _antialiasingTexture); - batch.setFramebuffer(framebufferCache->getLightingFramebuffer()); + batch.setFramebuffer(sourceBuffer); batch.setPipeline(getBlendPipeline()); DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index 6386622675..f0992ed843 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -26,11 +26,11 @@ public: class Antialiasing { public: using Config = AntiAliasingConfig; - using JobModel = render::Job::Model; + using JobModel = render::Job::ModelI; Antialiasing(); void configure(const Config& config) {} - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceBuffer); const gpu::PipelinePointer& getAntialiasingPipeline(); const gpu::PipelinePointer& getBlendPipeline(); diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index 6dfec30b16..8118df5435 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -32,7 +32,7 @@ void DebugDeferredBufferConfig::setMode(int newMode) { if (newMode == mode) { return; } else if (newMode > DebugDeferredBuffer::CustomMode || newMode < 0) { - mode = DebugDeferredBuffer::CustomMode; + mode = 0; } else { mode = newMode; } @@ -46,7 +46,12 @@ enum Slot { Depth, Lighting, Shadow, - Pyramid, + LinearDepth, + HalfLinearDepth, + HalfNormal, + Curvature, + DiffusedCurvature, + Scattering, AmbientOcclusion, AmbientOcclusionBlurred }; @@ -56,7 +61,7 @@ enum Slot { static const std::string DEFAULT_ALBEDO_SHADER { "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" - " return vec4(pow(frag.diffuse, vec3(1.0 / 2.2)), 1.0);" + " return vec4(pow(frag.albedo, vec3(1.0 / 2.2)), 1.0);" " }" }; @@ -83,28 +88,35 @@ static const std::string DEFAULT_NORMAL_SHADER { static const std::string DEFAULT_OCCLUSION_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" - " return vec4(vec3(frag.obscurance), 1.0);" + " return vec4(vec3(pow(frag.obscurance, 1.0 / 2.2)), 1.0);" " }" }; static const std::string DEFAULT_EMISSIVE_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" - " return (frag.mode == FRAG_MODE_SHADED ? vec4(pow(frag.emissive, vec3(1.0 / 2.2)), 1.0) : vec4(vec3(0.0), 1.0));" + " return (frag.mode == FRAG_MODE_SHADED ? vec4(pow(texture(specularMap, uv).rgb, vec3(1.0 / 2.2)), 1.0) : vec4(vec3(0.0), 1.0));" " }" }; static const std::string DEFAULT_UNLIT_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" - " return (frag.mode == FRAG_MODE_UNLIT ? vec4(pow(frag.diffuse, vec3(1.0 / 2.2)), 1.0) : vec4(vec3(0.0), 1.0));" + " return (frag.mode == FRAG_MODE_UNLIT ? vec4(pow(frag.albedo, vec3(1.0 / 2.2)), 1.0) : vec4(vec3(0.0), 1.0));" " }" }; static const std::string DEFAULT_LIGHTMAP_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" - " return (frag.mode == FRAG_MODE_LIGHTMAPPED ? vec4(pow(frag.emissive, vec3(1.0 / 2.2)), 1.0) : vec4(vec3(0.0), 1.0));" + " return (frag.mode == FRAG_MODE_LIGHTMAPPED ? vec4(pow(texture(specularMap, uv).rgb, vec3(1.0 / 2.2)), 1.0) : vec4(vec3(0.0), 1.0));" + " }" +}; + +static const std::string DEFAULT_SCATTERING_SHADER{ + "vec4 getFragmentColor() {" + " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" + " return (frag.mode == FRAG_MODE_SCATTERING ? vec4(vec3(pow(frag.scattering, 1.0 / 2.2)), 1.0) : vec4(vec3(0.0), 1.0));" " }" }; @@ -131,13 +143,63 @@ static const std::string DEFAULT_SHADOW_SHADER { " }" }; -static const std::string DEFAULT_PYRAMID_DEPTH_SHADER { +static const std::string DEFAULT_LINEAR_DEPTH_SHADER { "vec4 getFragmentColor() {" - " return vec4(vec3(1.0 - texture(pyramidMap, uv).x * 0.01), 1.0);" + " return vec4(vec3(1.0 - texture(linearDepthMap, uv).x * 0.01), 1.0);" + " }" +}; + +static const std::string DEFAULT_HALF_LINEAR_DEPTH_SHADER{ + "vec4 getFragmentColor() {" + " return vec4(vec3(1.0 - texture(halfLinearDepthMap, uv).x * 0.01), 1.0);" + " }" +}; + +static const std::string DEFAULT_HALF_NORMAL_SHADER{ + "vec4 getFragmentColor() {" + " return vec4(vec3(texture(halfNormalMap, uv).xyz), 1.0);" + " }" +}; + +static const std::string DEFAULT_CURVATURE_SHADER{ + "vec4 getFragmentColor() {" + " return vec4(pow(vec3(texture(curvatureMap, uv).a), vec3(1.0 / 2.2)), 1.0);" + // " return vec4(pow(vec3(texture(curvatureMap, uv).xyz), vec3(1.0 / 2.2)), 1.0);" //" return vec4(vec3(1.0 - textureLod(pyramidMap, uv, 3).x * 0.01), 1.0);" " }" }; +static const std::string DEFAULT_NORMAL_CURVATURE_SHADER{ + "vec4 getFragmentColor() {" + //" return vec4(pow(vec3(texture(curvatureMap, uv).a), vec3(1.0 / 2.2)), 1.0);" + " return vec4(vec3(texture(curvatureMap, uv).xyz), 1.0);" + //" return vec4(vec3(1.0 - textureLod(pyramidMap, uv, 3).x * 0.01), 1.0);" + " }" +}; + +static const std::string DEFAULT_DIFFUSED_CURVATURE_SHADER{ + "vec4 getFragmentColor() {" + " return vec4(pow(vec3(texture(diffusedCurvatureMap, uv).a), vec3(1.0 / 2.2)), 1.0);" + // " return vec4(pow(vec3(texture(curvatureMap, uv).xyz), vec3(1.0 / 2.2)), 1.0);" + //" return vec4(vec3(1.0 - textureLod(pyramidMap, uv, 3).x * 0.01), 1.0);" + " }" +}; + +static const std::string DEFAULT_DIFFUSED_NORMAL_CURVATURE_SHADER{ + "vec4 getFragmentColor() {" + //" return vec4(pow(vec3(texture(curvatureMap, uv).a), vec3(1.0 / 2.2)), 1.0);" + " return vec4(vec3(texture(diffusedCurvatureMap, uv).xyz), 1.0);" + //" return vec4(vec3(1.0 - textureLod(pyramidMap, uv, 3).x * 0.01), 1.0);" + " }" +}; + +static const std::string DEFAULT_DEBUG_SCATTERING_SHADER{ + "vec4 getFragmentColor() {" + " return vec4(pow(vec3(texture(scatteringMap, uv).xyz), vec3(1.0 / 2.2)), 1.0);" + // " return vec4(vec3(texture(scatteringMap, uv).xyz), 1.0);" + " }" +}; + static const std::string DEFAULT_AMBIENT_OCCLUSION_SHADER{ "vec4 getFragmentColor() {" " return vec4(vec3(texture(obscuranceMap, uv).x), 1.0);" @@ -197,18 +259,36 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, std::string cust return DEFAULT_OCCLUSION_SHADER; case LightmapMode: return DEFAULT_LIGHTMAP_SHADER; + case ScatteringMode: + return DEFAULT_SCATTERING_SHADER; case LightingMode: return DEFAULT_LIGHTING_SHADER; case ShadowMode: return DEFAULT_SHADOW_SHADER; - case PyramidDepthMode: - return DEFAULT_PYRAMID_DEPTH_SHADER; + case LinearDepthMode: + return DEFAULT_LINEAR_DEPTH_SHADER; + case HalfLinearDepthMode: + return DEFAULT_HALF_LINEAR_DEPTH_SHADER; + case HalfNormalMode: + return DEFAULT_HALF_NORMAL_SHADER; + case CurvatureMode: + return DEFAULT_CURVATURE_SHADER; + case NormalCurvatureMode: + return DEFAULT_NORMAL_CURVATURE_SHADER; + case DiffusedCurvatureMode: + return DEFAULT_DIFFUSED_CURVATURE_SHADER; + case DiffusedNormalCurvatureMode: + return DEFAULT_DIFFUSED_NORMAL_CURVATURE_SHADER; + case ScatteringDebugMode: + return DEFAULT_DEBUG_SCATTERING_SHADER; case AmbientOcclusionMode: return DEFAULT_AMBIENT_OCCLUSION_SHADER; case AmbientOcclusionBlurredMode: return DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER; case CustomMode: return getFileContent(customFile, DEFAULT_CUSTOM_SHADER); + default: + return DEFAULT_ALBEDO_SHADER; } Q_UNREACHABLE(); return std::string(); @@ -256,7 +336,12 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str slotBindings.insert(gpu::Shader::Binding("obscuranceMap", AmbientOcclusion)); slotBindings.insert(gpu::Shader::Binding("lightingMap", Lighting)); slotBindings.insert(gpu::Shader::Binding("shadowMap", Shadow)); - slotBindings.insert(gpu::Shader::Binding("pyramidMap", Pyramid)); + slotBindings.insert(gpu::Shader::Binding("linearDepthMap", LinearDepth)); + slotBindings.insert(gpu::Shader::Binding("halfLinearDepthMap", HalfLinearDepth)); + slotBindings.insert(gpu::Shader::Binding("halfNormalMap", HalfNormal)); + slotBindings.insert(gpu::Shader::Binding("curvatureMap", Curvature)); + slotBindings.insert(gpu::Shader::Binding("diffusedCurvatureMap", DiffusedCurvature)); + slotBindings.insert(gpu::Shader::Binding("scatteringMap", Scattering)); slotBindings.insert(gpu::Shader::Binding("occlusionBlurredMap", AmbientOcclusionBlurred)); gpu::Shader::makeProgram(*program, slotBindings); @@ -282,12 +367,23 @@ void DebugDeferredBuffer::configure(const Config& config) { _size = config.size; } -void DebugDeferredBuffer::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { +void DebugDeferredBuffer::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const Inputs& inputs) { + if (_mode == Off) { + return; + } + assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; + auto& deferredFramebuffer = inputs.get0(); + auto& linearDepthTarget = inputs.get1(); + auto& surfaceGeometryFramebuffer = inputs.get2(); + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setViewportTransform(args->_viewport); + const auto geometryBuffer = DependencyManager::get(); const auto framebufferCache = DependencyManager::get(); const auto textureCache = DependencyManager::get(); @@ -306,13 +402,18 @@ void DebugDeferredBuffer::run(const SceneContextPointer& sceneContext, const Ren batch.setPipeline(getPipeline(_mode, first)); - batch.setResourceTexture(Albedo, framebufferCache->getDeferredColorTexture()); - batch.setResourceTexture(Normal, framebufferCache->getDeferredNormalTexture()); - batch.setResourceTexture(Specular, framebufferCache->getDeferredSpecularTexture()); - batch.setResourceTexture(Depth, framebufferCache->getPrimaryDepthTexture()); - batch.setResourceTexture(Lighting, framebufferCache->getLightingTexture()); + batch.setResourceTexture(Albedo, deferredFramebuffer->getDeferredColorTexture()); + batch.setResourceTexture(Normal, deferredFramebuffer->getDeferredNormalTexture()); + batch.setResourceTexture(Specular, deferredFramebuffer->getDeferredSpecularTexture()); + batch.setResourceTexture(Depth, deferredFramebuffer->getPrimaryDepthTexture()); + batch.setResourceTexture(Lighting, deferredFramebuffer->getLightingTexture()); batch.setResourceTexture(Shadow, lightStage.lights[0]->shadow.framebuffer->getDepthStencilBuffer()); - batch.setResourceTexture(Pyramid, framebufferCache->getDepthPyramidTexture()); + batch.setResourceTexture(LinearDepth, linearDepthTarget->getLinearDepthTexture()); + batch.setResourceTexture(HalfLinearDepth, linearDepthTarget->getHalfLinearDepthTexture()); + batch.setResourceTexture(HalfNormal, linearDepthTarget->getHalfNormalTexture()); + + batch.setResourceTexture(Curvature, surfaceGeometryFramebuffer->getCurvatureTexture()); + batch.setResourceTexture(DiffusedCurvature, surfaceGeometryFramebuffer->getLowCurvatureTexture()); if (DependencyManager::get()->isAmbientOcclusionEnabled()) { batch.setResourceTexture(AmbientOcclusion, framebufferCache->getOcclusionTexture()); } else { diff --git a/libraries/render-utils/src/DebugDeferredBuffer.h b/libraries/render-utils/src/DebugDeferredBuffer.h index 521dc13e0a..d6005de349 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.h +++ b/libraries/render-utils/src/DebugDeferredBuffer.h @@ -15,6 +15,8 @@ #include #include +#include "DeferredFramebuffer.h" +#include "SurfaceGeometryPass.h" class DebugDeferredBufferConfig : public render::Job::Config { Q_OBJECT @@ -34,20 +36,22 @@ signals: class DebugDeferredBuffer { public: + using Inputs = render::VaryingSet4; using Config = DebugDeferredBufferConfig; - using JobModel = render::Job::Model; + using JobModel = render::Job::ModelI; DebugDeferredBuffer(); void configure(const Config& config); - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs); protected: friend class DebugDeferredBufferConfig; enum Mode : uint8_t { // Use Mode suffix to avoid collisions - DepthMode = 0, + Off = 0, + DepthMode, AlbedoMode, NormalMode, RoughnessMode, @@ -56,23 +60,33 @@ protected: UnlitMode, OcclusionMode, LightmapMode, + ScatteringMode, LightingMode, ShadowMode, - PyramidDepthMode, + LinearDepthMode, + HalfLinearDepthMode, + HalfNormalMode, + CurvatureMode, + NormalCurvatureMode, + DiffusedCurvatureMode, + DiffusedNormalCurvatureMode, + ScatteringDebugMode, AmbientOcclusionMode, AmbientOcclusionBlurredMode, - CustomMode // Needs to stay last + CustomMode, // Needs to stay last + + NumModes, }; private: - Mode _mode; + Mode _mode{ Off }; glm::vec4 _size; struct CustomPipeline { gpu::PipelinePointer pipeline; mutable QFileInfo info; }; - using StandardPipelines = std::array; + using StandardPipelines = std::array; using CustomPipelines = std::unordered_map; bool pipelineNeedsUpdate(Mode mode, std::string customFile = std::string()) const; diff --git a/libraries/render-utils/src/DeferredBuffer.slh b/libraries/render-utils/src/DeferredBuffer.slh index aed89b30d0..a4b69bd70e 100755 --- a/libraries/render-utils/src/DeferredBuffer.slh +++ b/libraries/render-utils/src/DeferredBuffer.slh @@ -11,6 +11,8 @@ <@if not DEFERRED_BUFFER_SLH@> <@def DEFERRED_BUFFER_SLH@> +<@include gpu/PackedNormal.slh@> + // Unpack the metallic-mode value const float FRAG_PACK_SHADED_NON_METALLIC = 0.0; const float FRAG_PACK_SHADED_METALLIC = 0.1; @@ -20,11 +22,16 @@ const float FRAG_PACK_LIGHTMAPPED_NON_METALLIC = 0.2; const float FRAG_PACK_LIGHTMAPPED_METALLIC = 0.3; const float FRAG_PACK_LIGHTMAPPED_RANGE_INV = 1.0 / (FRAG_PACK_LIGHTMAPPED_METALLIC - FRAG_PACK_LIGHTMAPPED_NON_METALLIC); -const float FRAG_PACK_UNLIT = 0.5; +const float FRAG_PACK_SCATTERING_NON_METALLIC = 0.4; +const float FRAG_PACK_SCATTERING_METALLIC = 0.5; +const float FRAG_PACK_SCATTERING_RANGE_INV = 1.0 / (FRAG_PACK_SCATTERING_METALLIC - FRAG_PACK_SCATTERING_NON_METALLIC); + +const float FRAG_PACK_UNLIT = 0.6; const int FRAG_MODE_UNLIT = 0; const int FRAG_MODE_SHADED = 1; const int FRAG_MODE_LIGHTMAPPED = 2; +const int FRAG_MODE_SCATTERING = 3; void unpackModeMetallic(float rawValue, out int mode, out float metallic) { if (rawValue <= FRAG_PACK_SHADED_METALLIC) { @@ -32,7 +39,10 @@ void unpackModeMetallic(float rawValue, out int mode, out float metallic) { metallic = clamp((rawValue - FRAG_PACK_SHADED_NON_METALLIC) * FRAG_PACK_SHADED_RANGE_INV, 0.0, 1.0); } else if (rawValue <= FRAG_PACK_LIGHTMAPPED_METALLIC) { mode = FRAG_MODE_LIGHTMAPPED; - metallic = clamp((rawValue - FRAG_PACK_LIGHTMAPPED_NON_METALLIC) * FRAG_PACK_SHADED_RANGE_INV, 0.0, 1.0); + metallic = clamp((rawValue - FRAG_PACK_LIGHTMAPPED_NON_METALLIC) * FRAG_PACK_LIGHTMAPPED_RANGE_INV, 0.0, 1.0); + } else if (rawValue <= FRAG_PACK_SCATTERING_METALLIC) { + mode = FRAG_MODE_SCATTERING; + metallic = clamp((rawValue - FRAG_PACK_SCATTERING_NON_METALLIC) * FRAG_PACK_SCATTERING_RANGE_INV, 0.0, 1.0); } else if (rawValue >= FRAG_PACK_UNLIT) { mode = FRAG_MODE_UNLIT; metallic = 0.0; @@ -47,8 +57,33 @@ float packLightmappedMetallic(float metallic) { return mix(FRAG_PACK_LIGHTMAPPED_NON_METALLIC, FRAG_PACK_LIGHTMAPPED_METALLIC, metallic); } +float packScatteringMetallic(float metallic) { + return mix(FRAG_PACK_SCATTERING_NON_METALLIC, FRAG_PACK_SCATTERING_METALLIC, metallic); +} + float packUnlit() { return FRAG_PACK_UNLIT; } + + <@endif@> diff --git a/libraries/render-utils/src/DeferredBufferRead.slh b/libraries/render-utils/src/DeferredBufferRead.slh index 569063955d..6b5060cc5f 100644 --- a/libraries/render-utils/src/DeferredBufferRead.slh +++ b/libraries/render-utils/src/DeferredBufferRead.slh @@ -32,97 +32,78 @@ uniform sampler2D obscuranceMap; uniform sampler2D lightingMap; -struct DeferredTransform { - mat4 projection; - mat4 viewInverse; - float stereoSide; - vec3 _spareABC; -}; - -layout(std140) uniform deferredTransformBuffer { - DeferredTransform _deferredTransform; -}; -DeferredTransform getDeferredTransform() { - return _deferredTransform; -} - -bool getStereoMode(DeferredTransform deferredTransform) { - return (deferredTransform.stereoSide != 0.0); -} -float getStereoSide(DeferredTransform deferredTransform) { - return (deferredTransform.stereoSide); -} - -vec4 evalEyePositionFromZ(DeferredTransform deferredTransform, float depthVal, vec2 texcoord) { - vec3 nPos = vec3(texcoord.xy * 2.0f - 1.0f, depthVal * 2.0f - 1.0f); - - // compute the view space position using the depth - // basically manually pick the proj matrix components to do the inverse - float Ze = -deferredTransform.projection[3][2] / (nPos.z + deferredTransform.projection[2][2]); - float Xe = (-Ze * nPos.x - Ze * deferredTransform.projection[2][0] - deferredTransform.projection[3][0]) / deferredTransform.projection[0][0]; - float Ye = (-Ze * nPos.y - Ze * deferredTransform.projection[2][1] - deferredTransform.projection[3][1]) / deferredTransform.projection[1][1]; - return vec4(Xe, Ye, Ze, 1.0f); -} - struct DeferredFragment { - vec4 normalVal; - vec4 diffuseVal; - vec4 specularVal; vec4 position; vec3 normal; float metallic; - vec3 diffuse; + vec3 albedo; float obscurance; - vec3 specular; + vec3 fresnel; float roughness; - vec3 emissive; int mode; + float scattering; float depthVal; }; -vec4 unpackDeferredPosition(DeferredTransform deferredTransform, float depthValue, vec2 texcoord) { - if (getStereoMode(deferredTransform)) { - if (texcoord.x > 0.5) { - texcoord.x -= 0.5; - } - texcoord.x *= 2.0; - } - return evalEyePositionFromZ(deferredTransform, depthValue, texcoord); -} - DeferredFragment unpackDeferredFragmentNoPosition(vec2 texcoord) { - + vec4 normalVal; + vec4 diffuseVal; + vec4 specularVal; + DeferredFragment frag; frag.depthVal = -1; - frag.normalVal = texture(normalMap, texcoord); - frag.diffuseVal = texture(albedoMap, texcoord); - frag.specularVal = texture(specularMap, texcoord); + normalVal = texture(normalMap, texcoord); + diffuseVal = texture(albedoMap, texcoord); + specularVal = texture(specularMap, texcoord); frag.obscurance = texture(obscuranceMap, texcoord).x; // Unpack the normal from the map - frag.normal = normalize(frag.normalVal.xyz * 2.0 - vec3(1.0)); - frag.roughness = frag.normalVal.a; + frag.normal = unpackNormal(normalVal.xyz); + frag.roughness = normalVal.a; // Diffuse color and unpack the mode and the metallicness - frag.diffuse = frag.diffuseVal.xyz; - unpackModeMetallic(frag.diffuseVal.w, frag.mode, frag.metallic); + frag.albedo = diffuseVal.xyz; + frag.scattering = 0.0; + unpackModeMetallic(diffuseVal.w, frag.mode, frag.metallic); + //frag.emissive = specularVal.xyz; + frag.obscurance = min(specularVal.w, frag.obscurance); + + + if (frag.mode == FRAG_MODE_SCATTERING) { + frag.scattering = specularVal.x; + } if (frag.metallic <= 0.5) { frag.metallic = 0.0; - frag.specular = vec3(0.03); // Default Di-electric fresnel value + frag.fresnel = vec3(0.03); // Default Di-electric fresnel value } else { - frag.specular = vec3(frag.diffuseVal.xyz); + frag.fresnel = vec3(diffuseVal.xyz); frag.metallic = 1.0; } - frag.emissive = frag.specularVal.xyz; - frag.obscurance = min(frag.specularVal.w, frag.obscurance); - return frag; } -DeferredFragment unpackDeferredFragment(DeferredTransform deferredTransform, vec2 texcoord) { + +<@include DeferredTransform.slh@> +<$declareDeferredFrameTransform()$> + +vec4 unpackDeferredPosition(DeferredFrameTransform deferredTransform, float depthValue, vec2 texcoord) { + int side = 0; + if (isStereo()) { + if (texcoord.x > 0.5) { + texcoord.x -= 0.5; + side = 1; + } + texcoord.x *= 2.0; + } + float Zeye = evalZeyeFromZdb(depthValue); + + return vec4(evalEyePositionFromZeye(side, Zeye, texcoord), 1.0); +} + +DeferredFragment unpackDeferredFragment(DeferredFrameTransform deferredTransform, vec2 texcoord) { float depthValue = texture(depthMap, texcoord).r; @@ -136,5 +117,30 @@ DeferredFragment unpackDeferredFragment(DeferredTransform deferredTransform, vec +<@func declareDeferredCurvature()@> + +// the curvature texture +uniform sampler2D curvatureMap; + +vec4 fetchCurvature(vec2 texcoord) { + return texture(curvatureMap, texcoord); +} + +// the curvature texture +uniform sampler2D diffusedCurvatureMap; + +vec4 fetchDiffusedCurvature(vec2 texcoord) { + return texture(diffusedCurvatureMap, texcoord); +} + +void unpackMidLowNormalCurvature(vec2 texcoord, out vec4 midNormalCurvature, out vec4 lowNormalCurvature) { + midNormalCurvature = fetchCurvature(texcoord); + lowNormalCurvature = fetchDiffusedCurvature(texcoord); + midNormalCurvature.xyz = normalize((midNormalCurvature.xyz - 0.5f) * 2.0f); + lowNormalCurvature.xyz = normalize((lowNormalCurvature.xyz - 0.5f) * 2.0f); + midNormalCurvature.w = (midNormalCurvature.w * 2 - 1); + lowNormalCurvature.w = (lowNormalCurvature.w * 2 - 1); +} +<@endfunc@> <@endif@> diff --git a/libraries/render-utils/src/DeferredBufferWrite.slh b/libraries/render-utils/src/DeferredBufferWrite.slh index 2be38fbea3..3153a851fb 100755 --- a/libraries/render-utils/src/DeferredBufferWrite.slh +++ b/libraries/render-utils/src/DeferredBufferWrite.slh @@ -13,28 +13,12 @@ <@include DeferredBuffer.slh@> + layout(location = 0) out vec4 _fragColor0; layout(location = 1) out vec4 _fragColor1; layout(location = 2) out vec4 _fragColor2; -uniform sampler2D normalFittingMap; - -vec3 bestFitNormal(vec3 normal) { - vec3 absNorm = abs(normal); - float maxNAbs = max(absNorm.z, max(absNorm.x, absNorm.y)); - - vec2 texcoord = (absNorm.z < maxNAbs ? - (absNorm.y < maxNAbs ? absNorm.yz : absNorm.xz) : - absNorm.xy); - texcoord = (texcoord.x < texcoord.y ? texcoord.yx : texcoord.xy); - texcoord.y /= texcoord.x; - vec3 cN = normal / maxNAbs; - - float fittingScale = texture(normalFittingMap, texcoord).a; - cN *= fittingScale; - return (cN * 0.5 + 0.5); -} - +layout(location = 3) out vec4 _fragColor3; // the alpha threshold const float alphaThreshold = 0.5; @@ -48,25 +32,30 @@ const float DEFAULT_METALLIC = 0; const vec3 DEFAULT_SPECULAR = vec3(0.1); const vec3 DEFAULT_EMISSIVE = vec3(0.0); const float DEFAULT_OCCLUSION = 1.0; +const float DEFAULT_SCATTERING = 0.0; const vec3 DEFAULT_FRESNEL = DEFAULT_EMISSIVE; -void packDeferredFragment(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 emissive, float occlusion) { +void packDeferredFragment(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 emissive, float occlusion, float scattering) { if (alpha != 1.0) { discard; } - _fragColor0 = vec4(albedo, packShadedMetallic(metallic)); - _fragColor1 = vec4(bestFitNormal(normal), clamp(roughness, 0.0, 1.0)); - _fragColor2 = vec4(emissive, occlusion); + _fragColor0 = vec4(albedo, ((scattering > 0.0) ? packScatteringMetallic(metallic) : packShadedMetallic(metallic))); + _fragColor1 = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); + _fragColor2 = vec4(((scattering > 0.0) ? vec3(scattering) : emissive), occlusion); + + _fragColor3 = vec4(emissive, 1.0); } - -void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 fresnel, vec3 emissive) { +void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 fresnel, vec3 lightmap) { if (alpha != 1.0) { discard; } + _fragColor0 = vec4(albedo, packLightmappedMetallic(metallic)); - _fragColor1 = vec4(bestFitNormal(normal), clamp(roughness, 0.0, 1.0)); - _fragColor2 = vec4(emissive, 1.0); + _fragColor1 = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); + _fragColor2 = vec4(lightmap, 1.0); + + _fragColor3 = vec4(lightmap * albedo, 1.0); } void packDeferredFragmentUnlit(vec3 normal, float alpha, vec3 color) { @@ -74,8 +63,9 @@ void packDeferredFragmentUnlit(vec3 normal, float alpha, vec3 color) { discard; } _fragColor0 = vec4(color, packUnlit()); - _fragColor1 = vec4(bestFitNormal(normal), 1.0); - //_fragColor2 = vec4(vec3(0.0), 1.0); // If unlit, do not worry about the emissive color target + _fragColor1 = vec4(packNormal(normal), 1.0); + // _fragColor2 = vec4(vec3(0.0), 1.0); + _fragColor3 = vec4(color, 1.0); } void packDeferredFragmentTranslucent(vec3 normal, float alpha, vec3 albedo, vec3 fresnel, float roughness) { diff --git a/libraries/render-utils/src/DeferredFrameTransform.cpp b/libraries/render-utils/src/DeferredFrameTransform.cpp new file mode 100644 index 0000000000..2f9d7b016a --- /dev/null +++ b/libraries/render-utils/src/DeferredFrameTransform.cpp @@ -0,0 +1,71 @@ +// +// DeferredFrameTransform.cpp +// libraries/render-utils/src/ +// +// Created by Sam Gateau 6/3/2016. +// Copyright 2016 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 "DeferredFrameTransform.h" + +#include "gpu/Context.h" +#include "render/Engine.h" + +DeferredFrameTransform::DeferredFrameTransform() { + FrameTransform frameTransform; + _frameTransformBuffer = gpu::BufferView(std::make_shared(sizeof(FrameTransform), (const gpu::Byte*) &frameTransform)); +} + +void DeferredFrameTransform::update(RenderArgs* args) { + + // Update the depth info with near and far (same for stereo) + auto nearZ = args->getViewFrustum().getNearClip(); + auto farZ = args->getViewFrustum().getFarClip(); + + auto& frameTransformBuffer = _frameTransformBuffer.edit(); + frameTransformBuffer.depthInfo = glm::vec4(nearZ*farZ, farZ - nearZ, -farZ, 0.0f); + + frameTransformBuffer.pixelInfo = args->_viewport; + + //_parametersBuffer.edit()._ditheringInfo.y += 0.25f; + + Transform cameraTransform; + args->getViewFrustum().evalViewTransform(cameraTransform); + cameraTransform.getMatrix(frameTransformBuffer.invView); + cameraTransform.getInverseMatrix(frameTransformBuffer.view); + + args->getViewFrustum().evalProjectionMatrix(frameTransformBuffer.projectionMono); + + // Running in stero ? + bool isStereo = args->_context->isStereo(); + if (!isStereo) { + frameTransformBuffer.projection[0] = frameTransformBuffer.projectionMono; + frameTransformBuffer.stereoInfo = glm::vec4(0.0f, (float)args->_viewport.z, 0.0f, 0.0f); + frameTransformBuffer.invpixelInfo = glm::vec4(1.0f / args->_viewport.z, 1.0f / args->_viewport.w, 0.0f, 0.0f); + } else { + + mat4 projMats[2]; + mat4 eyeViews[2]; + args->_context->getStereoProjections(projMats); + args->_context->getStereoViews(eyeViews); + + for (int i = 0; i < 2; i++) { + // Compose the mono Eye space to Stereo clip space Projection Matrix + auto sideViewMat = projMats[i] * eyeViews[i]; + frameTransformBuffer.projection[i] = sideViewMat; + } + + frameTransformBuffer.stereoInfo = glm::vec4(1.0f, (float)(args->_viewport.z >> 1), 0.0f, 1.0f); + frameTransformBuffer.invpixelInfo = glm::vec4(1.0f / (float)(args->_viewport.z >> 1), 1.0f / args->_viewport.w, 0.0f, 0.0f); + + } +} + +void GenerateDeferredFrameTransform::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, DeferredFrameTransformPointer& frameTransform) { + if (!frameTransform) { + frameTransform = std::make_shared(); + } + frameTransform->update(renderContext->args); +} diff --git a/libraries/render-utils/src/DeferredFrameTransform.h b/libraries/render-utils/src/DeferredFrameTransform.h new file mode 100644 index 0000000000..df47396a38 --- /dev/null +++ b/libraries/render-utils/src/DeferredFrameTransform.h @@ -0,0 +1,78 @@ +// +// DeferredFrameTransform.h +// libraries/render-utils/src/ +// +// Created by Sam Gateau 6/3/2016. +// Copyright 2016 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_DeferredFrameTransform_h +#define hifi_DeferredFrameTransform_h + +#include "gpu/Resource.h" +#include "render/DrawTask.h" + +class RenderArgs; + +// DeferredFrameTransform is a helper class gathering in one place the needed camera transform +// and frame resolution needed for all the deferred rendering passes taking advantage of the Deferred buffers +class DeferredFrameTransform { +public: + using UniformBufferView = gpu::BufferView; + + DeferredFrameTransform(); + + void update(RenderArgs* args); + + UniformBufferView getFrameTransformBuffer() const { return _frameTransformBuffer; } + +protected: + + + // Class describing the uniform buffer with the transform info common to the AO shaders + // It s changing every frame + class FrameTransform { + public: + // Pixel info is { viewport width height} + glm::vec4 pixelInfo; + glm::vec4 invpixelInfo; + // Depth info is { n.f, f - n, -f} + glm::vec4 depthInfo; + // Stereo info is { isStereoFrame, halfWidth } + glm::vec4 stereoInfo{ 0.0 }; + // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space + glm::mat4 projection[2]; + // THe mono projection for sure + glm::mat4 projectionMono; + // Inv View matrix from eye space (mono) to world space + glm::mat4 invView; + // View matrix from world space to eye space (mono) + glm::mat4 view; + + FrameTransform() {} + }; + UniformBufferView _frameTransformBuffer; + + +}; + +using DeferredFrameTransformPointer = std::shared_ptr; + + + + +class GenerateDeferredFrameTransform { +public: + using JobModel = render::Job::ModelO; + + GenerateDeferredFrameTransform() {} + + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, DeferredFrameTransformPointer& frameTransform); + +private: +}; + +#endif // hifi_DeferredFrameTransform_h diff --git a/libraries/render-utils/src/DeferredFramebuffer.cpp b/libraries/render-utils/src/DeferredFramebuffer.cpp new file mode 100644 index 0000000000..32f91f83d8 --- /dev/null +++ b/libraries/render-utils/src/DeferredFramebuffer.cpp @@ -0,0 +1,143 @@ +// +// DeferredFramebuffer.h +// libraries/render-utils/src/ +// +// Created by Sam Gateau 7/11/2016. +// Copyright 2016 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 "DeferredFramebuffer.h" + + +DeferredFramebuffer::DeferredFramebuffer() { +} + + +void DeferredFramebuffer::updatePrimaryDepth(const gpu::TexturePointer& depthBuffer) { + //If the depth buffer or size changed, we need to delete our FBOs + bool reset = false; + if ((_primaryDepthTexture != depthBuffer)) { + _primaryDepthTexture = depthBuffer; + reset = true; + } + if (_primaryDepthTexture) { + auto newFrameSize = glm::ivec2(_primaryDepthTexture->getDimensions()); + if (_frameSize != newFrameSize) { + _frameSize = newFrameSize; + reset = true; + } + } + + if (reset) { + _deferredFramebuffer.reset(); + _deferredFramebufferDepthColor.reset(); + _deferredColorTexture.reset(); + _deferredNormalTexture.reset(); + _deferredSpecularTexture.reset(); + _lightingTexture.reset(); + _lightingFramebuffer.reset(); + } +} + +void DeferredFramebuffer::allocate() { + + _deferredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + _deferredFramebufferDepthColor = gpu::FramebufferPointer(gpu::Framebuffer::create()); + + auto colorFormat = gpu::Element::COLOR_SRGBA_32; + auto linearFormat = gpu::Element::COLOR_RGBA_32; + auto width = _frameSize.x; + auto height = _frameSize.y; + + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); + + _deferredColorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); + + _deferredNormalTexture = gpu::TexturePointer(gpu::Texture::create2D(linearFormat, width, height, defaultSampler)); + _deferredSpecularTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); + + _deferredFramebuffer->setRenderBuffer(0, _deferredColorTexture); + _deferredFramebuffer->setRenderBuffer(1, _deferredNormalTexture); + _deferredFramebuffer->setRenderBuffer(2, _deferredSpecularTexture); + + _deferredFramebufferDepthColor->setRenderBuffer(0, _deferredColorTexture); + + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format + if (!_primaryDepthTexture) { + _primaryDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, width, height, defaultSampler)); + } + + _deferredFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); + + _deferredFramebufferDepthColor->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); + + + auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR); + + _lightingTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, defaultSampler)); + _lightingFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + _lightingFramebuffer->setRenderBuffer(0, _lightingTexture); + _lightingFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); + + _deferredFramebuffer->setRenderBuffer(3, _lightingTexture); + +} + + +gpu::TexturePointer DeferredFramebuffer::getPrimaryDepthTexture() { + if (!_primaryDepthTexture) { + allocate(); + } + return _primaryDepthTexture; +} + +gpu::FramebufferPointer DeferredFramebuffer::getDeferredFramebuffer() { + if (!_deferredFramebuffer) { + allocate(); + } + return _deferredFramebuffer; +} + +gpu::FramebufferPointer DeferredFramebuffer::getDeferredFramebufferDepthColor() { + if (!_deferredFramebufferDepthColor) { + allocate(); + } + return _deferredFramebufferDepthColor; +} + +gpu::TexturePointer DeferredFramebuffer::getDeferredColorTexture() { + if (!_deferredColorTexture) { + allocate(); + } + return _deferredColorTexture; +} + +gpu::TexturePointer DeferredFramebuffer::getDeferredNormalTexture() { + if (!_deferredNormalTexture) { + allocate(); + } + return _deferredNormalTexture; +} + +gpu::TexturePointer DeferredFramebuffer::getDeferredSpecularTexture() { + if (!_deferredSpecularTexture) { + allocate(); + } + return _deferredSpecularTexture; +} + +gpu::FramebufferPointer DeferredFramebuffer::getLightingFramebuffer() { + if (!_lightingFramebuffer) { + allocate(); + } + return _lightingFramebuffer; +} + +gpu::TexturePointer DeferredFramebuffer::getLightingTexture() { + if (!_lightingTexture) { + allocate(); + } + return _lightingTexture; +} diff --git a/libraries/render-utils/src/DeferredFramebuffer.h b/libraries/render-utils/src/DeferredFramebuffer.h new file mode 100644 index 0000000000..6c83fbb91f --- /dev/null +++ b/libraries/render-utils/src/DeferredFramebuffer.h @@ -0,0 +1,60 @@ +// +// DeferredFramebuffer.h +// libraries/render-utils/src/ +// +// Created by Sam Gateau 7/11/2016. +// Copyright 2016 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_DeferredFramebuffer_h +#define hifi_DeferredFramebuffer_h + +#include "gpu/Resource.h" +#include "gpu/Framebuffer.h" + +class RenderArgs; + +// DeferredFramebuffer is a helper class gathering in one place the GBuffer (Framebuffer) and lighting framebuffer +class DeferredFramebuffer { +public: + DeferredFramebuffer(); + + gpu::FramebufferPointer getDeferredFramebuffer(); + gpu::FramebufferPointer getDeferredFramebufferDepthColor(); + + gpu::TexturePointer getDeferredColorTexture(); + gpu::TexturePointer getDeferredNormalTexture(); + gpu::TexturePointer getDeferredSpecularTexture(); + + gpu::FramebufferPointer getLightingFramebuffer(); + gpu::TexturePointer getLightingTexture(); + + // Update the depth buffer which will drive the allocation of all the other resources according to its size. + void updatePrimaryDepth(const gpu::TexturePointer& depthBuffer); + gpu::TexturePointer getPrimaryDepthTexture(); + const glm::ivec2& getFrameSize() const { return _frameSize; } + +protected: + void allocate(); + + gpu::TexturePointer _primaryDepthTexture; + + gpu::FramebufferPointer _deferredFramebuffer; + gpu::FramebufferPointer _deferredFramebufferDepthColor; + + gpu::TexturePointer _deferredColorTexture; + gpu::TexturePointer _deferredNormalTexture; + gpu::TexturePointer _deferredSpecularTexture; + + gpu::TexturePointer _lightingTexture; + gpu::FramebufferPointer _lightingFramebuffer; + + glm::ivec2 _frameSize; +}; + +using DeferredFramebufferPointer = std::shared_ptr; + +#endif // hifi_DeferredFramebuffer_h \ No newline at end of file diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index 7608c8ae08..16cdab535c 100755 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -12,118 +12,123 @@ <@def DEFERRED_GLOBAL_LIGHT_SLH@> <@include model/Light.slh@> -<@include DeferredLighting.slh@> -<@func declareSkyboxMap()@> -// declareSkyboxMap -uniform samplerCube skyboxMap; +<@include LightingModel.slh@> -vec4 evalSkyboxLight(vec3 direction, float lod) { - // textureQueryLevels is not available until #430, so we require explicit lod - // float mipmapLevel = lod * textureQueryLevels(skyboxMap); - return textureLod(skyboxMap, direction, lod); -} -<@endfunc@> +<@include LightAmbient.slh@> +<@include LightDirectional.slh@> -<@func declareEvalGlobalSpecularIrradiance(supportAmbientSphere, supportAmbientMap, supportIfAmbientMapElseAmbientSphere)@> -vec3 fresnelSchlickAmbient(vec3 fresnelColor, vec3 lightDir, vec3 halfDir, float gloss) { - return fresnelColor + (max(vec3(gloss), fresnelColor) - fresnelColor) * pow(1.0 - clamp(dot(lightDir, halfDir), 0.0, 1.0), 5); -} - -<@if supportAmbientMap@> - <$declareSkyboxMap()$> -<@endif@> - -vec3 evalGlobalSpecularIrradiance(Light light, vec3 fragEyeDir, vec3 fragNormal, float roughness, vec3 fresnel, float obscurance) { - vec3 direction = -reflect(fragEyeDir, fragNormal); - vec3 ambientFresnel = fresnelSchlickAmbient(fresnel, fragEyeDir, fragNormal, 1 - roughness); - vec3 specularLight; - <@if supportIfAmbientMapElseAmbientSphere@> - if (getLightHasAmbientMap(light)) - <@endif@> - <@if supportAmbientMap@> - { - float levels = getLightAmbientMapNumMips(light); - float lod = min(floor((roughness) * levels), levels); - specularLight = evalSkyboxLight(direction, lod).xyz; - } - <@endif@> - <@if supportIfAmbientMapElseAmbientSphere@> - else - <@endif@> - <@if supportAmbientSphere@> - { - specularLight = evalSphericalLight(getLightAmbientSphere(light), direction).xyz; - } - <@endif@> - - return specularLight * ambientFresnel * getLightAmbientIntensity(light); -} -<@endfunc@> - -<@func prepareGlobalLight()@> +<@func prepareGlobalLight(isScattering)@> // prepareGlobalLight - // Transform directions to worldspace - vec3 fragNormal = vec3(invViewMat * vec4(normal, 0.0)); + vec3 fragNormal = vec3((normal)); vec3 fragEyeVector = vec3(invViewMat * vec4(-position, 0.0)); vec3 fragEyeDir = normalize(fragEyeVector); // Get light Light light = getLight(); - vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value - if (metallic > 0.5) { - fresnel = albedo; - metallic = 1.0; - } - vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, metallic, fresnel, roughness); - vec3 color = vec3(albedo * shading.w + shading.rgb) * min(shadowAttenuation, obscurance) * getLightColor(light) * getLightIntensity(light); - color += emissive; + + vec3 color = vec3(0.0); + <@endfunc@> <@func declareEvalAmbientGlobalColor()@> -vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) { +vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, float roughness) { <$prepareGlobalLight()$> color += albedo * getLightColor(light) * obscurance * getLightAmbientIntensity(light); return color; } <@endfunc@> -<@func declareEvalAmbientSphereGlobalColor()@> -<$declareEvalGlobalSpecularIrradiance(1, 0, 0)$> +<@func declareEvalAmbientSphereGlobalColor(supportScattering)@> + +<$declareLightingAmbient(1, _SCRIBE_NULL, _SCRIBE_NULL, $supportScattering$)$> +<$declareLightingDirectional($supportScattering$)$> + +<@if supportScattering@> +<$declareDeferredCurvature()$> +<@endif@> + +vec3 evalAmbientSphereGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, +vec3 albedo, vec3 fresnel, float metallic, float roughness +<@if supportScattering@> + , float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature +<@endif@> ) { + + <$prepareGlobalLight($supportScattering$)$> + + // Ambient + vec3 ambientDiffuse; + vec3 ambientSpecular; + evalLightingAmbient(ambientDiffuse, ambientSpecular, light, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, obscurance +<@if supportScattering@> + ,scattering, midNormalCurvature, lowNormalCurvature +<@endif@> ); + color += ambientDiffuse; + color += ambientSpecular; -vec3 evalAmbientSphereGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) { - <$prepareGlobalLight()$> - - // Diffuse from ambient - color += (1 - metallic) * albedo * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light); - - // Specular highlight from ambient - vec3 specularLighting = evalGlobalSpecularIrradiance(light, fragEyeDir, fragNormal, roughness, fresnel, obscurance); - color += specularLighting; + // Directional + vec3 directionalDiffuse; + vec3 directionalSpecular; + evalLightingDirectional(directionalDiffuse, directionalSpecular, light, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation +<@if supportScattering@> + ,scattering, midNormalCurvature, lowNormalCurvature +<@endif@> ); + color += directionalDiffuse; + color += directionalSpecular; return color; } + <@endfunc@> -<@func declareEvalSkyboxGlobalColor()@> -<$declareEvalGlobalSpecularIrradiance(0, 1, 0)$> -vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) { - <$prepareGlobalLight()$> +<@func declareEvalSkyboxGlobalColor(supportScattering)@> - // Diffuse from ambient - color += (1 - metallic) * albedo * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light); +<$declareLightingAmbient(_SCRIBE_NULL, 1, _SCRIBE_NULL, $supportScattering$)$> +<$declareLightingDirectional($supportScattering$)$> - // Specular highlight from ambient - vec3 specularLighting = evalGlobalSpecularIrradiance(light, fragEyeDir, fragNormal, roughness, fresnel, obscurance); - color += specularLighting; +<@if supportScattering@> +<$declareDeferredCurvature()$> +<@endif@> + +vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, + vec3 albedo, vec3 fresnel, float metallic, float roughness +<@if supportScattering@> + , float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature +<@endif@> + ) { + <$prepareGlobalLight($supportScattering$)$> + + // Ambient + vec3 ambientDiffuse; + vec3 ambientSpecular; + evalLightingAmbient(ambientDiffuse, ambientSpecular, light, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, obscurance +<@if supportScattering@> + ,scattering, midNormalCurvature, lowNormalCurvature +<@endif@> + ); + color += ambientDiffuse; + color += ambientSpecular; + + + // Directional + vec3 directionalDiffuse; + vec3 directionalSpecular; + evalLightingDirectional(directionalDiffuse, directionalSpecular, light, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation +<@if supportScattering@> + ,scattering, midNormalCurvature, lowNormalCurvature +<@endif@> + ); + color += directionalDiffuse; + color += directionalSpecular; return color; } + <@endfunc@> <@func declareEvalLightmappedColor()@> @@ -146,25 +151,37 @@ vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscur // Ambient light is the lightmap when in shadow vec3 ambientLight = (1 - lightAttenuation) * lightmap * getLightAmbientIntensity(light); - return obscurance * albedo * (diffuseLight + ambientLight); + return isLightmapEnabled() * obscurance * albedo * (diffuseLight + ambientLight); } <@endfunc@> + <@func declareEvalGlobalLightingAlphaBlended()@> -<$declareEvalGlobalSpecularIrradiance(1, 1, 1)$> +<$declareLightingAmbient(1, 1, 1)$> +<$declareLightingDirectional()$> -vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness, float opacity) { +vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity) { <$prepareGlobalLight()$> + + color += emissive * isEmissiveEnabled(); - // Diffuse from ambient - color += (1 - metallic) * albedo * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light); + // Ambient + vec3 ambientDiffuse; + vec3 ambientSpecular; + evalLightingAmbient(ambientDiffuse, ambientSpecular, light, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, obscurance); + color += ambientDiffuse; + color += ambientSpecular / opacity; - // Specular highlight from ambient - vec3 specularLighting = evalGlobalSpecularIrradiance(light, fragEyeDir, fragNormal, roughness, fresnel, obscurance); - color += specularLighting / opacity; + + // Directional + vec3 directionalDiffuse; + vec3 directionalSpecular; + evalLightingDirectional(directionalDiffuse, directionalSpecular, light, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation); + color += directionalDiffuse; + color += directionalSpecular / opacity; return color; } diff --git a/libraries/render-utils/src/DeferredLighting.slh b/libraries/render-utils/src/DeferredLighting.slh index 2e06deb0af..06d103080e 100755 --- a/libraries/render-utils/src/DeferredLighting.slh +++ b/libraries/render-utils/src/DeferredLighting.slh @@ -7,74 +7,4 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -!> -<@if not DEFERRED_LIGHTING_SLH@> -<@def DEFERRED_LIGHTING_SLH@> - - -<@func declareEvalPBRShading()@> - -vec3 fresnelSchlick(vec3 fresnelColor, vec3 lightDir, vec3 halfDir) { - return fresnelColor + (1.0 - fresnelColor) * pow(1.0 - clamp(dot(lightDir, halfDir), 0.0, 1.0), 5); -} - -float specularDistribution(float roughness, vec3 normal, vec3 halfDir) { - float ndoth = clamp(dot(halfDir, normal), 0.0, 1.0); - float gloss2 = pow(0.001 + roughness, 4); - float denom = (ndoth * ndoth*(gloss2 - 1) + 1); - float power = gloss2 / (3.14159 * denom * denom); - return power; -} - -// Frag Shading returns the diffuse amount as W and the specular rgb as xyz -vec4 evalPBRShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, float metallic, vec3 fresnel, float roughness) { - // Diffuse Lighting - float diffuse = clamp(dot(fragNormal, fragLightDir), 0.0, 1.0); - - // Specular Lighting - vec3 halfDir = normalize(fragEyeDir + fragLightDir); - vec3 fresnelColor = fresnelSchlick(fresnel, fragLightDir,halfDir); - float power = specularDistribution(roughness, fragNormal, halfDir); - vec3 specular = power * fresnelColor * diffuse; - - return vec4(specular, (1.0 - metallic) * diffuse * (1 - fresnelColor.x)); -} -<@endfunc@> - -<@func declareEvalBlinnRShading()@> - -vec4 evalBlinnShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, vec3 specular, float roughness) { - // Diffuse Lighting - float diffuseDot = dot(fragNormal, fragLightDir); - float facingLight = step(0.0, diffuseDot); - float diffuse = diffuseDot * facingLight; - - // Specular Lighting depends on the half vector and the roughness - vec3 halfDir = normalize(fragEyeDir + fragLightDir); - - float gloss = (1.0 - roughness) * 128.0; - glos *= gloss; - float specularPower = pow(facingLight * max(0.0, dot(halfDir, fragNormal)), gloss); - vec3 reflect = specularPower * specular * diffuse; - - return vec4(reflect, diffuse); -} - -<@endfunc@> - - -<$declareEvalPBRShading()$> - -// Return xyz the specular/reflection component and w the diffuse component -vec4 evalFragShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, float metallic, vec3 specular, float roughness) { - return evalPBRShading(fragNormal, fragLightDir, fragEyeDir, metallic, specular, roughness); -} - -<@endif@> +!> \ No newline at end of file diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 1d9ce65581..6f202f6200 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -38,17 +38,21 @@ #include "point_light_frag.h" #include "spot_light_frag.h" +using namespace render; + struct LightLocations { - int radius; - int ambientSphere; - int lightBufferUnit; - int texcoordMat; - int coneParam; - int deferredTransformBuffer; - int shadowTransformBuffer; + int radius{ -1 }; + int ambientSphere{ -1 }; + int lightBufferUnit{ -1 }; + int texcoordFrameTransform{ -1 }; + int sphereParam{ -1 }; + int coneParam{ -1 }; + int deferredFrameTransformBuffer{ -1 }; + int subsurfaceScatteringParametersBuffer{ -1 }; + int shadowTransformBuffer{ -1 }; }; -enum { +enum DeferredShader_MapSlot { DEFERRED_BUFFER_COLOR_UNIT = 0, DEFERRED_BUFFER_NORMAL_UNIT = 1, DEFERRED_BUFFER_EMISSIVE_UNIT = 2, @@ -56,7 +60,18 @@ enum { DEFERRED_BUFFER_OBSCURANCE_UNIT = 4, SHADOW_MAP_UNIT = 5, SKYBOX_MAP_UNIT = 6, + DEFERRED_BUFFER_CURVATURE_UNIT, + DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT, + SCATTERING_LUT_UNIT, + SCATTERING_SPECULAR_UNIT, }; +enum DeferredShader_BufferSlot { + DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT = 0, + SCATTERING_PARAMETERS_BUFFER_SLOT, + LIGHTING_MODEL_BUFFER_SLOT = render::ShapePipeline::Slot::LIGHTING_MODEL, + LIGHT_GPU_SLOT = render::ShapePipeline::Slot::LIGHT, +}; + static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); void DeferredLightingEffect::init() { @@ -132,372 +147,6 @@ void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radiu } } -void DeferredLightingEffect::prepare(RenderArgs* args) { - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - // Clear Lighting buffer - auto lightingFbo = DependencyManager::get()->getLightingFramebuffer(); - - batch.setFramebuffer(lightingFbo); - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(vec3(0), 0), true); - - // Clear deferred - auto deferredFbo = DependencyManager::get()->getDeferredFramebuffer(); - - batch.setFramebuffer(deferredFbo); - - // Clear Color, Depth and Stencil - batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_COLOR0 | - gpu::Framebuffer::BUFFER_DEPTH | - gpu::Framebuffer::BUFFER_STENCIL, - vec4(vec3(0), 1), 1.0, 0.0, true); - - // clear the normal and specular buffers - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR1, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), true); - const float MAX_SPECULAR_EXPONENT = 128.0f; - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR2, glm::vec4(0.0f, 0.0f, 0.0f, 1.0f / MAX_SPECULAR_EXPONENT), true); - }); -} - -void DeferredLightingEffect::render(const render::RenderContextPointer& renderContext) { - auto args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - - // Allocate the parameters buffer used by all the deferred shaders - if (!_deferredTransformBuffer[0]._buffer) { - DeferredTransform parameters; - _deferredTransformBuffer[0] = gpu::BufferView(std::make_shared(sizeof(DeferredTransform), (const gpu::Byte*) ¶meters)); - _deferredTransformBuffer[1] = gpu::BufferView(std::make_shared(sizeof(DeferredTransform), (const gpu::Byte*) ¶meters)); - } - - // Framebuffer copy operations cannot function as multipass stereo operations. - batch.enableStereo(false); - - // perform deferred lighting, rendering to free fbo - auto framebufferCache = DependencyManager::get(); - auto textureCache = DependencyManager::get(); - - QSize framebufferSize = framebufferCache->getFrameBufferSize(); - - // binding the first framebuffer - auto lightingFBO = framebufferCache->getLightingFramebuffer(); - batch.setFramebuffer(lightingFBO); - - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - // Bind the G-Buffer surfaces - batch.setResourceTexture(DEFERRED_BUFFER_COLOR_UNIT, framebufferCache->getDeferredColorTexture()); - batch.setResourceTexture(DEFERRED_BUFFER_NORMAL_UNIT, framebufferCache->getDeferredNormalTexture()); - batch.setResourceTexture(DEFERRED_BUFFER_EMISSIVE_UNIT, framebufferCache->getDeferredSpecularTexture()); - batch.setResourceTexture(DEFERRED_BUFFER_DEPTH_UNIT, framebufferCache->getPrimaryDepthTexture()); - - // FIXME: Different render modes should have different tasks - if (args->_renderMode == RenderArgs::DEFAULT_RENDER_MODE && _ambientOcclusionEnabled) { - batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, framebufferCache->getOcclusionTexture()); - } else { - // need to assign the white texture if ao is off - batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, textureCache->getWhiteTexture()); - } - - assert(_lightStage.lights.size() > 0); - const auto& globalShadow = _lightStage.lights[0]->shadow; - - // Bind the shadow buffer - batch.setResourceTexture(SHADOW_MAP_UNIT, globalShadow.map); - - // THe main viewport is assumed to be the mono viewport (or the 2 stereo faces side by side within that viewport) - auto monoViewport = args->_viewport; - float sMin = args->_viewport.x / (float)framebufferSize.width(); - float sWidth = args->_viewport.z / (float)framebufferSize.width(); - float tMin = args->_viewport.y / (float)framebufferSize.height(); - float tHeight = args->_viewport.w / (float)framebufferSize.height(); - - // The view frustum is the mono frustum base - auto viewFrustum = args->getViewFrustum(); - - // Eval the mono projection - mat4 monoProjMat; - viewFrustum.evalProjectionMatrix(monoProjMat); - - // The mono view transform - Transform monoViewTransform; - viewFrustum.evalViewTransform(monoViewTransform); - - // THe mono view matrix coming from the mono view transform - glm::mat4 monoViewMat; - monoViewTransform.getMatrix(monoViewMat); - - // Running in stero ? - bool isStereo = args->_context->isStereo(); - int numPasses = 1; - - mat4 projMats[2]; - Transform viewTransforms[2]; - ivec4 viewports[2]; - vec4 clipQuad[2]; - vec2 screenBottomLeftCorners[2]; - vec2 screenTopRightCorners[2]; - vec4 fetchTexcoordRects[2]; - - DeferredTransform deferredTransforms[2]; - auto geometryCache = DependencyManager::get(); - - if (isStereo) { - numPasses = 2; - - mat4 eyeViews[2]; - args->_context->getStereoProjections(projMats); - args->_context->getStereoViews(eyeViews); - - float halfWidth = 0.5f * sWidth; - - for (int i = 0; i < numPasses; i++) { - // In stereo, the 2 sides are layout side by side in the mono viewport and their width is half - int sideWidth = monoViewport.z >> 1; - viewports[i] = ivec4(monoViewport.x + (i * sideWidth), monoViewport.y, sideWidth, monoViewport.w); - - deferredTransforms[i].projection = projMats[i]; - - auto sideViewMat = monoViewMat * glm::inverse(eyeViews[i]); - // viewTransforms[i].evalFromRawMatrix(sideViewMat); - viewTransforms[i] = monoViewTransform; - viewTransforms[i].postTranslate(-glm::vec3((eyeViews[i][3])));// evalFromRawMatrix(sideViewMat); - deferredTransforms[i].viewInverse = sideViewMat; - - deferredTransforms[i].stereoSide = (i == 0 ? -1.0f : 1.0f); - - clipQuad[i] = glm::vec4(sMin + i * halfWidth, tMin, halfWidth, tHeight); - screenBottomLeftCorners[i] = glm::vec2(-1.0f + i * 1.0f, -1.0f); - screenTopRightCorners[i] = glm::vec2(i * 1.0f, 1.0f); - - fetchTexcoordRects[i] = glm::vec4(sMin + i * halfWidth, tMin, halfWidth, tHeight); - } - } else { - - viewports[0] = monoViewport; - projMats[0] = monoProjMat; - - deferredTransforms[0].projection = monoProjMat; - - deferredTransforms[0].viewInverse = monoViewMat; - viewTransforms[0] = monoViewTransform; - - deferredTransforms[0].stereoSide = 0.0f; - - clipQuad[0] = glm::vec4(sMin, tMin, sWidth, tHeight); - screenBottomLeftCorners[0] = glm::vec2(-1.0f, -1.0f); - screenTopRightCorners[0] = glm::vec2(1.0f, 1.0f); - - fetchTexcoordRects[0] = glm::vec4(sMin, tMin, sWidth, tHeight); - } - - auto eyePoint = viewFrustum.getPosition(); - float nearRadius = glm::distance(eyePoint, viewFrustum.getNearTopLeft()); - - - for (int side = 0; side < numPasses; side++) { - // Render in this side's viewport - batch.setViewportTransform(viewports[side]); - batch.setStateScissorRect(viewports[side]); - - // Sync and Bind the correct DeferredTransform ubo - _deferredTransformBuffer[side]._buffer->setSubData(0, sizeof(DeferredTransform), (const gpu::Byte*) &deferredTransforms[side]); - batch.setUniformBuffer(_directionalLightLocations->deferredTransformBuffer, _deferredTransformBuffer[side]); - - glm::vec2 topLeft(-1.0f, -1.0f); - glm::vec2 bottomRight(1.0f, 1.0f); - glm::vec2 texCoordTopLeft(clipQuad[side].x, clipQuad[side].y); - glm::vec2 texCoordBottomRight(clipQuad[side].x + clipQuad[side].z, clipQuad[side].y + clipQuad[side].w); - - // First Global directional light and ambient pass - { - auto& program = _shadowMapEnabled ? _directionalLightShadow : _directionalLight; - LightLocationsPtr locations = _shadowMapEnabled ? _directionalLightShadowLocations : _directionalLightLocations; - const auto& keyLight = _allocatedLights[_globalLights.front()]; - - // Setup the global directional pass pipeline - { - if (_shadowMapEnabled) { - if (keyLight->getAmbientMap()) { - program = _directionalSkyboxLightShadow; - locations = _directionalSkyboxLightShadowLocations; - } else { - program = _directionalAmbientSphereLightShadow; - locations = _directionalAmbientSphereLightShadowLocations; - } - } else { - if (keyLight->getAmbientMap()) { - program = _directionalSkyboxLight; - locations = _directionalSkyboxLightLocations; - } else { - program = _directionalAmbientSphereLight; - locations = _directionalAmbientSphereLightLocations; - } - } - - if (locations->shadowTransformBuffer >= 0) { - batch.setUniformBuffer(locations->shadowTransformBuffer, globalShadow.getBuffer()); - } - batch.setPipeline(program); - } - - { // Setup the global lighting - setupKeyLightBatch(batch, locations->lightBufferUnit, SKYBOX_MAP_UNIT); - } - - { - batch.setModelTransform(Transform()); - batch.setProjectionTransform(glm::mat4()); - batch.setViewTransform(Transform()); - - glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); - geometryCache->renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color); - } - - if (keyLight->getAmbientMap()) { - batch.setResourceTexture(SKYBOX_MAP_UNIT, nullptr); - } - } - - auto texcoordMat = glm::mat4(); - /* texcoordMat[0] = glm::vec4(sWidth / 2.0f, 0.0f, 0.0f, sMin + sWidth / 2.0f); - texcoordMat[1] = glm::vec4(0.0f, tHeight / 2.0f, 0.0f, tMin + tHeight / 2.0f); - */ texcoordMat[0] = glm::vec4(fetchTexcoordRects[side].z / 2.0f, 0.0f, 0.0f, fetchTexcoordRects[side].x + fetchTexcoordRects[side].z / 2.0f); - texcoordMat[1] = glm::vec4(0.0f, fetchTexcoordRects[side].w / 2.0f, 0.0f, fetchTexcoordRects[side].y + fetchTexcoordRects[side].w / 2.0f); - texcoordMat[2] = glm::vec4(0.0f, 0.0f, 1.0f, 0.0f); - texcoordMat[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); - - // enlarge the scales slightly to account for tesselation - const float SCALE_EXPANSION = 0.05f; - - - batch.setProjectionTransform(projMats[side]); - batch.setViewTransform(viewTransforms[side]); - - // Splat Point lights - if (!_pointLights.empty()) { - batch.setPipeline(_pointLight); - - batch._glUniformMatrix4fv(_pointLightLocations->texcoordMat, 1, false, reinterpret_cast< const float* >(&texcoordMat)); - - for (auto lightID : _pointLights) { - auto& light = _allocatedLights[lightID]; - // IN DEBUG: light->setShowContour(true); - batch.setUniformBuffer(_pointLightLocations->lightBufferUnit, light->getSchemaBuffer()); - - float expandedRadius = light->getMaximumRadius() * (1.0f + SCALE_EXPANSION); - // TODO: We shouldn;t have to do that test and use a different volume geometry for when inside the vlight volume, - // we should be able to draw thre same geometry use DepthClamp but for unknown reason it's s not working... - if (glm::distance(eyePoint, glm::vec3(light->getPosition())) < expandedRadius + nearRadius) { - Transform model; - model.setTranslation(glm::vec3(0.0f, 0.0f, -1.0f)); - batch.setModelTransform(model); - batch.setViewTransform(Transform()); - batch.setProjectionTransform(glm::mat4()); - - glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); - DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color); - - batch.setProjectionTransform(projMats[side]); - batch.setViewTransform(viewTransforms[side]); - } else { - Transform model; - model.setTranslation(glm::vec3(light->getPosition().x, light->getPosition().y, light->getPosition().z)); - batch.setModelTransform(model.postScale(expandedRadius)); - batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - geometryCache->renderSphere(batch); - } - } - } - - // Splat spot lights - if (!_spotLights.empty()) { - batch.setPipeline(_spotLight); - - batch._glUniformMatrix4fv(_spotLightLocations->texcoordMat, 1, false, reinterpret_cast< const float* >(&texcoordMat)); - - for (auto lightID : _spotLights) { - auto light = _allocatedLights[lightID]; - // IN DEBUG: light->setShowContour(true); - batch.setUniformBuffer(_spotLightLocations->lightBufferUnit, light->getSchemaBuffer()); - - auto eyeLightPos = eyePoint - light->getPosition(); - auto eyeHalfPlaneDistance = glm::dot(eyeLightPos, light->getDirection()); - - const float TANGENT_LENGTH_SCALE = 0.666f; - glm::vec4 coneParam(light->getSpotAngleCosSin(), TANGENT_LENGTH_SCALE * tanf(0.5f * light->getSpotAngle()), 1.0f); - - float expandedRadius = light->getMaximumRadius() * (1.0f + SCALE_EXPANSION); - // TODO: We shouldn;t have to do that test and use a different volume geometry for when inside the vlight volume, - // we should be able to draw thre same geometry use DepthClamp but for unknown reason it's s not working... - const float OVER_CONSERVATIVE_SCALE = 1.1f; - if ((eyeHalfPlaneDistance > -nearRadius) && - (glm::distance(eyePoint, glm::vec3(light->getPosition())) < (expandedRadius * OVER_CONSERVATIVE_SCALE) + nearRadius)) { - coneParam.w = 0.0f; - batch._glUniform4fv(_spotLightLocations->coneParam, 1, reinterpret_cast< const float* >(&coneParam)); - - Transform model; - model.setTranslation(glm::vec3(0.0f, 0.0f, -1.0f)); - batch.setModelTransform(model); - batch.setViewTransform(Transform()); - batch.setProjectionTransform(glm::mat4()); - - glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); - DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color); - - batch.setProjectionTransform( projMats[side]); - batch.setViewTransform(viewTransforms[side]); - } else { - light->setShowContour(false); - coneParam.w = 1.0f; - batch._glUniform4fv(_spotLightLocations->coneParam, 1, reinterpret_cast< const float* >(&coneParam)); - - Transform model; - model.setTranslation(light->getPosition()); - model.postRotate(light->getOrientation()); - model.postScale(glm::vec3(expandedRadius, expandedRadius, expandedRadius)); - - batch.setModelTransform(model); - auto mesh = getSpotLightMesh(); - - batch.setIndexBuffer(mesh->getIndexBuffer()); - batch.setInputBuffer(0, mesh->getVertexBuffer()); - batch.setInputFormat(mesh->getVertexFormat()); - - auto& part = mesh->getPartBuffer().get(); - - batch.drawIndexed(model::Mesh::topologyToPrimitive(part._topology), part._numIndices, part._startIndex); - } - } - } - } - - // Probably not necessary in the long run because the gpu layer would unbound this texture if used as render target - batch.setResourceTexture(DEFERRED_BUFFER_COLOR_UNIT, nullptr); - batch.setResourceTexture(DEFERRED_BUFFER_NORMAL_UNIT, nullptr); - batch.setResourceTexture(DEFERRED_BUFFER_EMISSIVE_UNIT, nullptr); - batch.setResourceTexture(DEFERRED_BUFFER_DEPTH_UNIT, nullptr); - batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, nullptr); - batch.setResourceTexture(SHADOW_MAP_UNIT, nullptr); - batch.setResourceTexture(SKYBOX_MAP_UNIT, nullptr); - - batch.setUniformBuffer(_directionalLightLocations->deferredTransformBuffer, nullptr); - }); - - // End of the Lighting pass - if (!_pointLights.empty()) { - _pointLights.clear(); - } - if (!_spotLights.empty()) { - _spotLights.clear(); - } -} - void DeferredLightingEffect::setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int skyboxCubemapUnit) { PerformanceTimer perfTimer("DLE->setupBatch()"); auto keyLight = _allocatedLights[_globalLights.front()]; @@ -526,21 +175,29 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo slotBindings.insert(gpu::Shader::Binding(std::string("shadowMap"), SHADOW_MAP_UNIT)); slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), SKYBOX_MAP_UNIT)); - static const int LIGHT_GPU_SLOT = 3; - static const int DEFERRED_TRANSFORM_BUFFER_SLOT = 2; + slotBindings.insert(gpu::Shader::Binding(std::string("curvatureMap"), DEFERRED_BUFFER_CURVATURE_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("diffusedCurvatureMap"), DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("scatteringLUT"), SCATTERING_LUT_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("scatteringSpecularBeckmann"), SCATTERING_SPECULAR_UNIT)); + + + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), LIGHTING_MODEL_BUFFER_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("subsurfaceScatteringParametersBuffer"), SCATTERING_PARAMETERS_BUFFER_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), LIGHT_GPU_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("deferredTransformBuffer"), DEFERRED_TRANSFORM_BUFFER_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); locations->radius = program->getUniforms().findLocation("radius"); locations->ambientSphere = program->getUniforms().findLocation("ambientSphere.L00"); - locations->texcoordMat = program->getUniforms().findLocation("texcoordMat"); + locations->texcoordFrameTransform = program->getUniforms().findLocation("texcoordFrameTransform"); + locations->sphereParam = program->getUniforms().findLocation("sphereParam"); locations->coneParam = program->getUniforms().findLocation("coneParam"); locations->lightBufferUnit = program->getBuffers().findLocation("lightBuffer"); - locations->deferredTransformBuffer = program->getBuffers().findLocation("deferredTransformBuffer"); + locations->deferredFrameTransformBuffer = program->getBuffers().findLocation("deferredFrameTransformBuffer"); + locations->subsurfaceScatteringParametersBuffer = program->getBuffers().findLocation("subsurfaceScatteringParametersBuffer"); locations->shadowTransformBuffer = program->getBuffers().findLocation("shadowTransformBuffer"); auto state = std::make_shared(); @@ -548,19 +205,19 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo // Stencil test all the light passes for objects pixels only, not the background state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); - + if (lightVolume) { state->setCullMode(gpu::State::CULL_BACK); - - // No need for z test since the depth buffer is not bound state->setDepthTest(true, false, gpu::LESS_EQUAL); - // TODO: We should bind the true depth buffer both as RT and texture for the depth test - // TODO: We should use DepthClamp and avoid changing geometry for inside /outside cases - state->setDepthClampEnable(true); + state->setDepthTest(true, false, gpu::LESS_EQUAL); + // TODO: We should use DepthClamp and avoid changing geometry for inside /outside cases // additive blending state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + } else { state->setCullMode(gpu::State::CULL_BACK); + // additive blending + state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE); } pipeline = gpu::Pipeline::create(program, state); @@ -662,11 +319,398 @@ model::MeshPointer DeferredLightingEffect::getSpotLightMesh() { _spotLightMesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(sizeof(unsigned short) * indices, (gpu::Byte*) indexData), gpu::Element::INDEX_UINT16)); delete[] indexData; - model::Mesh::Part part(0, indices, 0, model::Mesh::TRIANGLES); - //DEBUG: model::Mesh::Part part(0, indices, 0, model::Mesh::LINE_STRIP); - _spotLightMesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(sizeof(part), (gpu::Byte*) &part), gpu::Element::PART_DRAWCALL)); + std::vector parts; + parts.push_back(model::Mesh::Part(0, indices, 0, model::Mesh::TRIANGLES)); + parts.push_back(model::Mesh::Part(0, indices, 0, model::Mesh::LINE_STRIP)); // outline version + + + _spotLightMesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(model::Mesh::Part), (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); } return _spotLightMesh; } +void PreparePrimaryFramebuffer::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, gpu::FramebufferPointer& primaryFramebuffer) { + + auto framebufferCache = DependencyManager::get(); + auto framebufferSize = framebufferCache->getFrameBufferSize(); + glm::ivec2 frameSize(framebufferSize.width(), framebufferSize.height()); + + if (!_primaryFramebuffer) { + _primaryFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + auto colorFormat = gpu::Element::COLOR_SRGBA_32; + + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); + auto primaryColorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, frameSize.x, frameSize.y, defaultSampler)); + + + _primaryFramebuffer->setRenderBuffer(0, primaryColorTexture); + + + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format + auto primaryDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, frameSize.x, frameSize.y, defaultSampler)); + + _primaryFramebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat); + + } + _primaryFramebuffer->resize(frameSize.x, frameSize.y); + + primaryFramebuffer = _primaryFramebuffer; +} + +void PrepareDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + auto args = renderContext->args; + + auto primaryFramebuffer = inputs.get0(); + auto lightingModel = inputs.get1(); + + if (!_deferredFramebuffer) { + _deferredFramebuffer = std::make_shared(); + } + _deferredFramebuffer->updatePrimaryDepth(primaryFramebuffer->getDepthStencilBuffer()); + + outputs.edit0() = _deferredFramebuffer; + outputs.edit1() = _deferredFramebuffer->getLightingFramebuffer(); + + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + + // Clear deferred + auto deferredFbo = _deferredFramebuffer->getDeferredFramebuffer(); + batch.setFramebuffer(deferredFbo); + + // Clear Color, Depth and Stencil for deferred buffer + batch.clearFramebuffer( + gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_COLOR1 | gpu::Framebuffer::BUFFER_COLOR2 | gpu::Framebuffer::BUFFER_COLOR3 | + gpu::Framebuffer::BUFFER_DEPTH | + gpu::Framebuffer::BUFFER_STENCIL, + vec4(vec3(0), 0), 1.0, 0.0, true); + + // For the rest of the rendering, bind the lighting model + batch.setUniformBuffer(LIGHTING_MODEL_BUFFER_SLOT, lightingModel->getParametersBuffer()); + }); +} + + +void RenderDeferredSetup::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, + const DeferredFrameTransformPointer& frameTransform, + const DeferredFramebufferPointer& deferredFramebuffer, + const LightingModelPointer& lightingModel, + const SurfaceGeometryFramebufferPointer& surfaceGeometryFramebuffer, + const gpu::FramebufferPointer& lowCurvatureNormalFramebuffer, + const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource) { + + auto args = renderContext->args; + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + + // Framebuffer copy operations cannot function as multipass stereo operations. + batch.enableStereo(false); + + // perform deferred lighting, rendering to free fbo + auto framebufferCache = DependencyManager::get(); + + auto textureCache = DependencyManager::get(); + auto deferredLightingEffect = DependencyManager::get(); + + // binding the first framebuffer + auto lightingFBO = deferredFramebuffer->getLightingFramebuffer(); + batch.setFramebuffer(lightingFBO); + + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + + + // Bind the G-Buffer surfaces + batch.setResourceTexture(DEFERRED_BUFFER_COLOR_UNIT, deferredFramebuffer->getDeferredColorTexture()); + batch.setResourceTexture(DEFERRED_BUFFER_NORMAL_UNIT, deferredFramebuffer->getDeferredNormalTexture()); + batch.setResourceTexture(DEFERRED_BUFFER_EMISSIVE_UNIT, deferredFramebuffer->getDeferredSpecularTexture()); + batch.setResourceTexture(DEFERRED_BUFFER_DEPTH_UNIT, deferredFramebuffer->getPrimaryDepthTexture()); + + // FIXME: Different render modes should have different tasks + if (args->_renderMode == RenderArgs::DEFAULT_RENDER_MODE && deferredLightingEffect->isAmbientOcclusionEnabled()) { + batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, framebufferCache->getOcclusionTexture()); + } else { + // need to assign the white texture if ao is off + batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, textureCache->getWhiteTexture()); + } + + // The Deferred Frame Transform buffer + batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, frameTransform->getFrameTransformBuffer()); + + // THe lighting model + batch.setUniformBuffer(LIGHTING_MODEL_BUFFER_SLOT, lightingModel->getParametersBuffer()); + + // Subsurface scattering specific + if (surfaceGeometryFramebuffer) { + batch.setResourceTexture(DEFERRED_BUFFER_CURVATURE_UNIT, surfaceGeometryFramebuffer->getCurvatureTexture()); + } + if (lowCurvatureNormalFramebuffer) { + // batch.setResourceTexture(DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT, lowCurvatureNormalFramebuffer->getRenderBuffer(0)); + batch.setResourceTexture(DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT, surfaceGeometryFramebuffer->getLowCurvatureTexture()); + } + if (subsurfaceScatteringResource) { + batch.setUniformBuffer(SCATTERING_PARAMETERS_BUFFER_SLOT, subsurfaceScatteringResource->getParametersBuffer()); + batch.setResourceTexture(SCATTERING_LUT_UNIT, subsurfaceScatteringResource->getScatteringTable()); + batch.setResourceTexture(SCATTERING_SPECULAR_UNIT, subsurfaceScatteringResource->getScatteringSpecular()); + } + + // Global directional light and ambient pass + + assert(deferredLightingEffect->getLightStage().lights.size() > 0); + const auto& globalShadow = deferredLightingEffect->getLightStage().lights[0]->shadow; + + // Bind the shadow buffer + batch.setResourceTexture(SHADOW_MAP_UNIT, globalShadow.map); + + auto& program = deferredLightingEffect->_shadowMapEnabled ? deferredLightingEffect->_directionalLightShadow : deferredLightingEffect->_directionalLight; + LightLocationsPtr locations = deferredLightingEffect->_shadowMapEnabled ? deferredLightingEffect->_directionalLightShadowLocations : deferredLightingEffect->_directionalLightLocations; + const auto& keyLight = deferredLightingEffect->_allocatedLights[deferredLightingEffect->_globalLights.front()]; + + // Setup the global directional pass pipeline + { + if (deferredLightingEffect->_shadowMapEnabled) { + if (keyLight->getAmbientMap()) { + program = deferredLightingEffect->_directionalSkyboxLightShadow; + locations = deferredLightingEffect->_directionalSkyboxLightShadowLocations; + } else { + program = deferredLightingEffect->_directionalAmbientSphereLightShadow; + locations = deferredLightingEffect->_directionalAmbientSphereLightShadowLocations; + } + } else { + if (keyLight->getAmbientMap()) { + program = deferredLightingEffect->_directionalSkyboxLight; + locations = deferredLightingEffect->_directionalSkyboxLightLocations; + } else { + program = deferredLightingEffect->_directionalAmbientSphereLight; + locations = deferredLightingEffect->_directionalAmbientSphereLightLocations; + } + } + + if (locations->shadowTransformBuffer >= 0) { + batch.setUniformBuffer(locations->shadowTransformBuffer, globalShadow.getBuffer()); + } + batch.setPipeline(program); + } + + // Adjust the texcoordTransform in the case we are rendeirng a sub region(mini mirror) + auto textureFrameTransform = gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(deferredFramebuffer->getFrameSize(), args->_viewport); + batch._glUniform4fv(locations->texcoordFrameTransform, 1, reinterpret_cast< const float* >(&textureFrameTransform)); + + { // Setup the global lighting + deferredLightingEffect->setupKeyLightBatch(batch, locations->lightBufferUnit, SKYBOX_MAP_UNIT); + } + + batch.draw(gpu::TRIANGLE_STRIP, 4); + + if (keyLight->getAmbientMap()) { + batch.setResourceTexture(SKYBOX_MAP_UNIT, nullptr); + } + batch.setResourceTexture(SHADOW_MAP_UNIT, nullptr); + }); + +} + +void RenderDeferredLocals::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, + const DeferredFrameTransformPointer& frameTransform, + const DeferredFramebufferPointer& deferredFramebuffer, + const LightingModelPointer& lightingModel) { + + bool points = lightingModel->isPointLightEnabled(); + bool spots = lightingModel->isSpotLightEnabled(); + + if (!points && !spots) { + return; + } + auto args = renderContext->args; + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + + // THe main viewport is assumed to be the mono viewport (or the 2 stereo faces side by side within that viewport) + auto monoViewport = args->_viewport; + + // The view frustum is the mono frustum base + auto viewFrustum = args->getViewFrustum(); + + // Eval the mono projection + mat4 monoProjMat; + viewFrustum.evalProjectionMatrix(monoProjMat); + + // The mono view transform + Transform monoViewTransform; + viewFrustum.evalViewTransform(monoViewTransform); + + // THe mono view matrix coming from the mono view transform + glm::mat4 monoViewMat; + monoViewTransform.getMatrix(monoViewMat); + + auto geometryCache = DependencyManager::get(); + + auto eyePoint = viewFrustum.getPosition(); + float nearRadius = glm::distance(eyePoint, viewFrustum.getNearTopLeft()); + + auto deferredLightingEffect = DependencyManager::get(); + + // Render in this side's viewport + batch.setViewportTransform(monoViewport); + batch.setStateScissorRect(monoViewport); + + // enlarge the scales slightly to account for tesselation + const float SCALE_EXPANSION = 0.05f; + + auto textureFrameTransform = gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(deferredFramebuffer->getFrameSize(), monoViewport); + + batch.setProjectionTransform(monoProjMat); + batch.setViewTransform(monoViewTransform); + + // Splat Point lights + if (points && !deferredLightingEffect->_pointLights.empty()) { + // POint light pipeline + batch.setPipeline(deferredLightingEffect->_pointLight); + batch._glUniform4fv(deferredLightingEffect->_pointLightLocations->texcoordFrameTransform, 1, reinterpret_cast< const float* >(&textureFrameTransform)); + + for (auto lightID : deferredLightingEffect->_pointLights) { + auto& light = deferredLightingEffect->_allocatedLights[lightID]; + // IN DEBUG: light->setShowContour(true); + batch.setUniformBuffer(deferredLightingEffect->_pointLightLocations->lightBufferUnit, light->getSchemaBuffer()); + + float expandedRadius = light->getMaximumRadius() * (1.0f + SCALE_EXPANSION); + glm::vec4 sphereParam(expandedRadius, 0.0f, 0.0f, 1.0f); + + // TODO: We shouldn;t have to do that test and use a different volume geometry for when inside the vlight volume, + // we should be able to draw thre same geometry use DepthClamp but for unknown reason it's s not working... + if (glm::distance(eyePoint, glm::vec3(light->getPosition())) < expandedRadius + nearRadius) { + sphereParam.w = 0.0f; + batch._glUniform4fv(deferredLightingEffect->_pointLightLocations->sphereParam, 1, reinterpret_cast< const float* >(&sphereParam)); + batch.draw(gpu::TRIANGLE_STRIP, 4); + } else { + sphereParam.w = 1.0f; + batch._glUniform4fv(deferredLightingEffect->_pointLightLocations->sphereParam, 1, reinterpret_cast< const float* >(&sphereParam)); + + Transform model; + model.setTranslation(glm::vec3(light->getPosition().x, light->getPosition().y, light->getPosition().z)); + batch.setModelTransform(model.postScale(expandedRadius)); + batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + geometryCache->renderSphere(batch); + } + } + } + + // Splat spot lights + if (spots && !deferredLightingEffect->_spotLights.empty()) { + // Spot light pipeline + batch.setPipeline(deferredLightingEffect->_spotLight); + batch._glUniform4fv(deferredLightingEffect->_spotLightLocations->texcoordFrameTransform, 1, reinterpret_cast< const float* >(&textureFrameTransform)); + + // Spot mesh + auto mesh = deferredLightingEffect->getSpotLightMesh(); + batch.setIndexBuffer(mesh->getIndexBuffer()); + batch.setInputBuffer(0, mesh->getVertexBuffer()); + batch.setInputFormat(mesh->getVertexFormat()); + auto& conePart = mesh->getPartBuffer().get(0); + + for (auto lightID : deferredLightingEffect->_spotLights) { + auto light = deferredLightingEffect->_allocatedLights[lightID]; + // IN DEBUG: + // light->setShowContour(true); + batch.setUniformBuffer(deferredLightingEffect->_spotLightLocations->lightBufferUnit, light->getSchemaBuffer()); + + auto eyeLightPos = eyePoint - light->getPosition(); + auto eyeHalfPlaneDistance = glm::dot(eyeLightPos, light->getDirection()); + + const float TANGENT_LENGTH_SCALE = 0.666f; + glm::vec4 coneParam(light->getSpotAngleCosSin(), TANGENT_LENGTH_SCALE * tanf(0.5f * light->getSpotAngle()), 1.0f); + + float expandedRadius = light->getMaximumRadius() * (1.0f + SCALE_EXPANSION); + // TODO: We shouldn;t have to do that test and use a different volume geometry for when inside the vlight volume, + // we should be able to draw thre same geometry use DepthClamp but for unknown reason it's s not working... + const float OVER_CONSERVATIVE_SCALE = 1.1f; + if ((eyeHalfPlaneDistance > -nearRadius) && + (glm::distance(eyePoint, glm::vec3(light->getPosition())) < (expandedRadius * OVER_CONSERVATIVE_SCALE) + nearRadius)) { + coneParam.w = 0.0f; + batch._glUniform4fv(deferredLightingEffect->_spotLightLocations->coneParam, 1, reinterpret_cast< const float* >(&coneParam)); + batch.draw(gpu::TRIANGLE_STRIP, 4); + } else { + coneParam.w = 1.0f; + batch._glUniform4fv(deferredLightingEffect->_spotLightLocations->coneParam, 1, reinterpret_cast< const float* >(&coneParam)); + + Transform model; + model.setTranslation(light->getPosition()); + model.postRotate(light->getOrientation()); + model.postScale(glm::vec3(expandedRadius, expandedRadius, expandedRadius)); + + batch.setModelTransform(model); + + batch.drawIndexed(model::Mesh::topologyToPrimitive(conePart._topology), conePart._numIndices, conePart._startIndex); + } + } + } + }); +} + +void RenderDeferredCleanup::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { + auto args = renderContext->args; + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + // Probably not necessary in the long run because the gpu layer would unbound this texture if used as render target + batch.setResourceTexture(DEFERRED_BUFFER_COLOR_UNIT, nullptr); + batch.setResourceTexture(DEFERRED_BUFFER_NORMAL_UNIT, nullptr); + batch.setResourceTexture(DEFERRED_BUFFER_EMISSIVE_UNIT, nullptr); + batch.setResourceTexture(DEFERRED_BUFFER_DEPTH_UNIT, nullptr); + batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, nullptr); + + batch.setResourceTexture(DEFERRED_BUFFER_CURVATURE_UNIT, nullptr); + batch.setResourceTexture(DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT, nullptr); + batch.setResourceTexture(SCATTERING_LUT_UNIT, nullptr); + batch.setResourceTexture(SCATTERING_SPECULAR_UNIT, nullptr); + + batch.setUniformBuffer(SCATTERING_PARAMETERS_BUFFER_SLOT, nullptr); + // batch.setUniformBuffer(LIGHTING_MODEL_BUFFER_SLOT, nullptr); + batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, nullptr); + }); + + auto deferredLightingEffect = DependencyManager::get(); + + // End of the Lighting pass + if (!deferredLightingEffect->_pointLights.empty()) { + deferredLightingEffect->_pointLights.clear(); + } + if (!deferredLightingEffect->_spotLights.empty()) { + deferredLightingEffect->_spotLights.clear(); + } +} + +RenderDeferred::RenderDeferred() { + +} + + +void RenderDeferred::configure(const Config& config) { +} + +void RenderDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const Inputs& inputs) { + auto deferredTransform = inputs.get0(); + auto deferredFramebuffer = inputs.get1(); + auto lightingModel = inputs.get2(); + auto surfaceGeometryFramebuffer = inputs.get3(); + auto lowCurvatureNormalFramebuffer = inputs.get4(); + auto subsurfaceScatteringResource = inputs.get5(); + auto args = renderContext->args; + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + _gpuTimer.begin(batch); + }); + + setupJob.run(sceneContext, renderContext, deferredTransform, deferredFramebuffer, lightingModel, surfaceGeometryFramebuffer, lowCurvatureNormalFramebuffer, subsurfaceScatteringResource); + + lightsJob.run(sceneContext, renderContext, deferredTransform, deferredFramebuffer, lightingModel); + + cleanupJob.run(sceneContext, renderContext); + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + _gpuTimer.end(batch); + }); + + auto config = std::static_pointer_cast(renderContext->jobConfig); + config->gpuTime = _gpuTimer.getAverage(); +} diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 63d8f4d175..b309299be9 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -21,13 +21,21 @@ #include "model/Geometry.h" #include "render/Context.h" +#include + +#include "DeferredFrameTransform.h" +#include "DeferredFramebuffer.h" +#include "LightingModel.h" #include "LightStage.h" +#include "SurfaceGeometryPass.h" +#include "SubsurfaceScattering.h" class RenderArgs; struct LightLocations; using LightLocationsPtr = std::shared_ptr; -/// Handles deferred lighting for the bits that require it (voxels...) + +// THis is where we currently accumulate the local lights, let s change that sooner than later class DeferredLightingEffect : public Dependency { SINGLETON_DEPENDENCY @@ -42,9 +50,6 @@ public: void addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f), float intensity = 0.5f, float falloffRadius = 0.01f, const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI); - - void prepare(RenderArgs* args); - void render(const render::RenderContextPointer& renderContext); void setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int skyboxCubemapUnit); @@ -95,19 +100,100 @@ private: std::vector _globalLights; std::vector _pointLights; std::vector _spotLights; + + friend class RenderDeferredSetup; + friend class RenderDeferredLocals; + friend class RenderDeferredCleanup; +}; - // Class describing the uniform buffer with all the parameters common to the deferred shaders - class DeferredTransform { - public: - glm::mat4 projection; - glm::mat4 viewInverse; - float stereoSide { 0.f }; - float spareA, spareB, spareC; +class PreparePrimaryFramebuffer { +public: + using JobModel = render::Job::ModelO; - DeferredTransform() {} - }; - typedef gpu::BufferView UniformBufferView; - UniformBufferView _deferredTransformBuffer[2]; + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, gpu::FramebufferPointer& primaryFramebuffer); + + gpu::FramebufferPointer _primaryFramebuffer; +}; + +class PrepareDeferred { +public: + // Inputs: primaryFramebuffer and lightingModel + using Inputs = render::VaryingSet2 ; + // Output: DeferredFramebuffer, LightingFramebuffer + using Outputs = render::VaryingSet2; + + using JobModel = render::Job::ModelIO; + + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + + DeferredFramebufferPointer _deferredFramebuffer; +}; + +class RenderDeferredSetup { +public: + // using JobModel = render::Job::ModelI; + + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, + const DeferredFrameTransformPointer& frameTransform, + const DeferredFramebufferPointer& deferredFramebuffer, + const LightingModelPointer& lightingModel, + const SurfaceGeometryFramebufferPointer& surfaceGeometryFramebuffer, + const gpu::FramebufferPointer& lowCurvatureNormalFramebuffer, + const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource); +}; + +class RenderDeferredLocals { +public: + using JobModel = render::Job::ModelI; + + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, + const DeferredFrameTransformPointer& frameTransform, + const DeferredFramebufferPointer& deferredFramebuffer, + const LightingModelPointer& lightingModel); +}; + + +class RenderDeferredCleanup { +public: + using JobModel = render::Job::Model; + + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); +}; + + +class RenderDeferredConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(double gpuTime READ getGpuTime) +public: + RenderDeferredConfig() : render::Job::Config(true) {} + + double getGpuTime() { return gpuTime; } + + double gpuTime{ 0.0 }; + +signals: + void dirty(); +}; + + +class RenderDeferred { +public: + using Inputs = render::VaryingSet6 < DeferredFrameTransformPointer, DeferredFramebufferPointer, LightingModelPointer, SurfaceGeometryFramebufferPointer, gpu::FramebufferPointer, SubsurfaceScatteringResourcePointer>; + using Config = RenderDeferredConfig; + using JobModel = render::Job::ModelI; + + RenderDeferred(); + + void configure(const Config& config); + + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs); + + RenderDeferredSetup setupJob; + RenderDeferredLocals lightsJob; + RenderDeferredCleanup cleanupJob; + +protected: + gpu::RangeTimer _gpuTimer; }; #endif // hifi_DeferredLightingEffect_h diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh new file mode 100644 index 0000000000..b3881c4c71 --- /dev/null +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -0,0 +1,123 @@ + +<@if not DEFERRED_TRANSFORM_SLH@> +<@def DEFERRED_TRANSFORM_SLH@> + +<@func declareDeferredFrameTransform()@> + +struct DeferredFrameTransform { + vec4 _pixelInfo; + vec4 _invPixelInfo; + vec4 _depthInfo; + vec4 _stereoInfo; + mat4 _projection[2]; + mat4 _projectionMono; + mat4 _viewInverse; + mat4 _view; +}; + +uniform deferredFrameTransformBuffer { + DeferredFrameTransform frameTransform; +}; + +DeferredFrameTransform getDeferredFrameTransform() { + return frameTransform; +} + +vec2 getWidthHeight(int resolutionLevel) { + return vec2(ivec2(frameTransform._pixelInfo.zw) >> resolutionLevel); +} + +vec2 getInvWidthHeight() { + return frameTransform._invPixelInfo.xy; +} + +float getProjScaleEye() { + return frameTransform._projection[0][1][1]; +} + +float getProjScale(int resolutionLevel) { + return getWidthHeight(resolutionLevel).y * frameTransform._projection[0][1][1] * 0.5; +} +mat4 getProjection(int side) { + return frameTransform._projection[side]; +} +mat4 getProjectionMono() { + return frameTransform._projectionMono; +} + +// positive near distance of the projection +float getProjectionNear() { + float planeC = frameTransform._projection[0][2][3] + frameTransform._projection[0][2][2]; + float planeD = frameTransform._projection[0][3][2]; + return planeD / planeC; +} + +// positive far distance of the projection +float getPosLinearDepthFar() { + return -frameTransform._depthInfo.z; +} + +mat4 getViewInverse() { + return frameTransform._viewInverse; +} + +mat4 getView() { + return frameTransform._view; +} + +bool isStereo() { + return frameTransform._stereoInfo.x > 0.0f; +} + +float getStereoSideWidth(int resolutionLevel) { + return float(int(frameTransform._stereoInfo.y) >> resolutionLevel); +} + +ivec4 getStereoSideInfo(int xPos, int resolutionLevel) { + int sideWidth = int(getStereoSideWidth(resolutionLevel)); + return ivec4(xPos < sideWidth ? ivec2(0, 0) : ivec2(1, sideWidth), sideWidth, isStereo()); +} + +float evalZeyeFromZdb(float depth) { + return frameTransform._depthInfo.x / (depth * frameTransform._depthInfo.y + frameTransform._depthInfo.z); +} + +vec3 evalEyeNormal(vec3 C) { + //return normalize(cross(dFdy(C), dFdx(C))); + return normalize(cross(dFdx(C), dFdy(C))); +} + +vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) { + // compute the view space position using the depth + // basically manually pick the proj matrix components to do the inverse + float Xe = (-Zeye * (texcoord.x * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][0] - frameTransform._projection[side][3][0]) / frameTransform._projection[side][0][0]; + float Ye = (-Zeye * (texcoord.y * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][1] - frameTransform._projection[side][3][1]) / frameTransform._projection[side][1][1]; + return vec3(Xe, Ye, Zeye); +} + +ivec2 getPixelPosTexcoordPosAndSide(in vec2 glFragCoord, out ivec2 pixelPos, out vec2 texcoordPos, out ivec4 stereoSide) { + ivec2 fragPos = ivec2(glFragCoord.xy); + + stereoSide = getStereoSideInfo(fragPos.x, 0); + + pixelPos = fragPos; + pixelPos.x -= stereoSide.y; + + texcoordPos = (vec2(pixelPos) + 0.5) * getInvWidthHeight(); + + return fragPos; +} + +<@endfunc@> + + +<@endif@> \ No newline at end of file diff --git a/libraries/render-utils/src/FramebufferCache.cpp b/libraries/render-utils/src/FramebufferCache.cpp index 3223ee5535..5375de273a 100644 --- a/libraries/render-utils/src/FramebufferCache.cpp +++ b/libraries/render-utils/src/FramebufferCache.cpp @@ -33,20 +33,8 @@ void FramebufferCache::setFrameBufferSize(QSize frameBufferSize) { //If the size changed, we need to delete our FBOs if (_frameBufferSize != frameBufferSize) { _frameBufferSize = frameBufferSize; - _primaryFramebuffer.reset(); - _primaryDepthTexture.reset(); - _primaryColorTexture.reset(); - _deferredFramebuffer.reset(); - _deferredFramebufferDepthColor.reset(); - _deferredColorTexture.reset(); - _deferredNormalTexture.reset(); - _deferredSpecularTexture.reset(); _selfieFramebuffer.reset(); _cachedFramebuffers.clear(); - _lightingTexture.reset(); - _lightingFramebuffer.reset(); - _depthPyramidFramebuffer.reset(); - _depthPyramidTexture.reset(); _occlusionFramebuffer.reset(); _occlusionTexture.reset(); _occlusionBlurredFramebuffer.reset(); @@ -55,40 +43,11 @@ void FramebufferCache::setFrameBufferSize(QSize frameBufferSize) { } void FramebufferCache::createPrimaryFramebuffer() { - _primaryFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); - _deferredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); - _deferredFramebufferDepthColor = gpu::FramebufferPointer(gpu::Framebuffer::create()); - - // auto colorFormat = gpu::Element::COLOR_RGBA_32; auto colorFormat = gpu::Element::COLOR_SRGBA_32; auto width = _frameBufferSize.width(); auto height = _frameBufferSize.height(); auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _primaryColorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); - - _primaryFramebuffer->setRenderBuffer(0, _primaryColorTexture); - - _deferredColorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); - _deferredNormalTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); - _deferredSpecularTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); - - _deferredFramebuffer->setRenderBuffer(0, _deferredColorTexture); - _deferredFramebuffer->setRenderBuffer(1, _deferredNormalTexture); - _deferredFramebuffer->setRenderBuffer(2, _deferredSpecularTexture); - - _deferredFramebufferDepthColor->setRenderBuffer(0, _deferredColorTexture); - - // auto depthFormat = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH); - auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format - _primaryDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, width, height, defaultSampler)); - - _primaryFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); - - _deferredFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); - - _deferredFramebufferDepthColor->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); - _selfieFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); auto tex = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width * 0.5, height * 0.5, defaultSampler)); @@ -96,20 +55,8 @@ void FramebufferCache::createPrimaryFramebuffer() { auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR); - _lightingTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, defaultSampler)); - _lightingFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); - _lightingFramebuffer->setRenderBuffer(0, _lightingTexture); - _lightingFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); - // For AO: - auto pointMipSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_POINT); - _depthPyramidTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), width, height, pointMipSampler)); - _depthPyramidFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); - _depthPyramidFramebuffer->setRenderBuffer(0, _depthPyramidTexture); - _depthPyramidFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); - - resizeAmbientOcclusionBuffers(); } @@ -125,88 +72,19 @@ void FramebufferCache::resizeAmbientOcclusionBuffers() { auto height = _frameBufferSize.height() >> _AOResolutionLevel; auto colorFormat = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGB); auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); - auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format + // auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format _occlusionTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); _occlusionFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); _occlusionFramebuffer->setRenderBuffer(0, _occlusionTexture); - _occlusionFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); + // _occlusionFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); _occlusionBlurredTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); _occlusionBlurredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); _occlusionBlurredFramebuffer->setRenderBuffer(0, _occlusionBlurredTexture); - _occlusionBlurredFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); + // _occlusionBlurredFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); } -gpu::FramebufferPointer FramebufferCache::getPrimaryFramebuffer() { - if (!_primaryFramebuffer) { - createPrimaryFramebuffer(); - } - return _primaryFramebuffer; -} - -gpu::TexturePointer FramebufferCache::getPrimaryDepthTexture() { - if (!_primaryDepthTexture) { - createPrimaryFramebuffer(); - } - return _primaryDepthTexture; -} - -gpu::TexturePointer FramebufferCache::getPrimaryColorTexture() { - if (!_primaryColorTexture) { - createPrimaryFramebuffer(); - } - return _primaryColorTexture; -} - -gpu::FramebufferPointer FramebufferCache::getDeferredFramebuffer() { - if (!_deferredFramebuffer) { - createPrimaryFramebuffer(); - } - return _deferredFramebuffer; -} - -gpu::FramebufferPointer FramebufferCache::getDeferredFramebufferDepthColor() { - if (!_deferredFramebufferDepthColor) { - createPrimaryFramebuffer(); - } - return _deferredFramebufferDepthColor; -} - -gpu::TexturePointer FramebufferCache::getDeferredColorTexture() { - if (!_deferredColorTexture) { - createPrimaryFramebuffer(); - } - return _deferredColorTexture; -} - -gpu::TexturePointer FramebufferCache::getDeferredNormalTexture() { - if (!_deferredNormalTexture) { - createPrimaryFramebuffer(); - } - return _deferredNormalTexture; -} - -gpu::TexturePointer FramebufferCache::getDeferredSpecularTexture() { - if (!_deferredSpecularTexture) { - createPrimaryFramebuffer(); - } - return _deferredSpecularTexture; -} - -gpu::FramebufferPointer FramebufferCache::getLightingFramebuffer() { - if (!_lightingFramebuffer) { - createPrimaryFramebuffer(); - } - return _lightingFramebuffer; -} - -gpu::TexturePointer FramebufferCache::getLightingTexture() { - if (!_lightingTexture) { - createPrimaryFramebuffer(); - } - return _lightingTexture; -} gpu::FramebufferPointer FramebufferCache::getFramebuffer() { if (_cachedFramebuffers.isEmpty()) { @@ -230,20 +108,6 @@ gpu::FramebufferPointer FramebufferCache::getSelfieFramebuffer() { return _selfieFramebuffer; } -gpu::FramebufferPointer FramebufferCache::getDepthPyramidFramebuffer() { - if (!_depthPyramidFramebuffer) { - createPrimaryFramebuffer(); - } - return _depthPyramidFramebuffer; -} - -gpu::TexturePointer FramebufferCache::getDepthPyramidTexture() { - if (!_depthPyramidTexture) { - createPrimaryFramebuffer(); - } - return _depthPyramidTexture; -} - void FramebufferCache::setAmbientOcclusionResolutionLevel(int level) { const int MAX_AO_RESOLUTION_LEVEL = 4; level = std::max(0, std::min(level, MAX_AO_RESOLUTION_LEVEL)); diff --git a/libraries/render-utils/src/FramebufferCache.h b/libraries/render-utils/src/FramebufferCache.h index 7c7c309572..d3d26c35b0 100644 --- a/libraries/render-utils/src/FramebufferCache.h +++ b/libraries/render-utils/src/FramebufferCache.h @@ -30,32 +30,11 @@ public: void setFrameBufferSize(QSize frameBufferSize); const QSize& getFrameBufferSize() const { return _frameBufferSize; } - /// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is - /// used for scene rendering. - gpu::FramebufferPointer getPrimaryFramebuffer(); - - gpu::TexturePointer getPrimaryDepthTexture(); - gpu::TexturePointer getPrimaryColorTexture(); - - gpu::FramebufferPointer getDeferredFramebuffer(); - gpu::FramebufferPointer getDeferredFramebufferDepthColor(); - - gpu::TexturePointer getDeferredColorTexture(); - gpu::TexturePointer getDeferredNormalTexture(); - gpu::TexturePointer getDeferredSpecularTexture(); - - gpu::FramebufferPointer getDepthPyramidFramebuffer(); - gpu::TexturePointer getDepthPyramidTexture(); - void setAmbientOcclusionResolutionLevel(int level); gpu::FramebufferPointer getOcclusionFramebuffer(); gpu::TexturePointer getOcclusionTexture(); gpu::FramebufferPointer getOcclusionBlurredFramebuffer(); gpu::TexturePointer getOcclusionBlurredTexture(); - - - gpu::TexturePointer getLightingTexture(); - gpu::FramebufferPointer getLightingFramebuffer(); /// Returns the framebuffer object used to render selfie maps; gpu::FramebufferPointer getSelfieFramebuffer(); @@ -73,29 +52,10 @@ private: void createPrimaryFramebuffer(); - gpu::FramebufferPointer _primaryFramebuffer; - - gpu::TexturePointer _primaryDepthTexture; - gpu::TexturePointer _primaryColorTexture; - - gpu::FramebufferPointer _deferredFramebuffer; - gpu::FramebufferPointer _deferredFramebufferDepthColor; - - gpu::TexturePointer _deferredColorTexture; - gpu::TexturePointer _deferredNormalTexture; - gpu::TexturePointer _deferredSpecularTexture; - - gpu::TexturePointer _lightingTexture; - gpu::FramebufferPointer _lightingFramebuffer; - gpu::FramebufferPointer _shadowFramebuffer; gpu::FramebufferPointer _selfieFramebuffer; - gpu::FramebufferPointer _depthPyramidFramebuffer; - gpu::TexturePointer _depthPyramidTexture; - - gpu::FramebufferPointer _occlusionFramebuffer; gpu::TexturePointer _occlusionTexture; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 15bf44744c..50a93d2200 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -11,13 +11,16 @@ #include "GeometryCache.h" + #include -#include -#include +#include +#include +#include #include #include +#include #include "TextureCache.h" #include "RenderUtilsLogging.h" @@ -32,6 +35,9 @@ #include "simple_vert.h" #include "simple_textured_frag.h" #include "simple_textured_unlit_frag.h" +#include "glowLine_vert.h" +#include "glowLine_geom.h" +#include "glowLine_frag.h" #include "grid_frag.h" @@ -42,19 +48,31 @@ const int GeometryCache::UNKNOWN_ID = -1; static const int VERTICES_PER_TRIANGLE = 3; -static const gpu::Element POSITION_ELEMENT{ gpu::VEC3, gpu::FLOAT, gpu::XYZ }; -static const gpu::Element NORMAL_ELEMENT{ gpu::VEC3, gpu::FLOAT, gpu::XYZ }; -static const gpu::Element COLOR_ELEMENT{ gpu::VEC4, gpu::NUINT8, gpu::RGBA }; +static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; +static const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; +static const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::NUINT8, gpu::RGBA }; static gpu::Stream::FormatPointer SOLID_STREAM_FORMAT; static gpu::Stream::FormatPointer INSTANCED_SOLID_STREAM_FORMAT; static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec3) * 2; // vertices and normals static const uint SHAPE_NORMALS_OFFSET = sizeof(glm::vec3); -static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT16; -static const uint SHAPE_INDEX_SIZE = sizeof(gpu::uint16); +static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT32; +static const uint SHAPE_INDEX_SIZE = sizeof(gpu::uint32); -void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const VertexVector& vertices) { +template +std::vector polygon() { + std::vector result; + result.reserve(SIDES); + double angleIncrement = 2.0 * M_PI / SIDES; + for (size_t i = 0; i < SIDES; ++i) { + double angle = (double)i * angleIncrement; + result.push_back(vec3{ cos(angle) * 0.5, 0.0, sin(angle) * 0.5 }); + } + return result; +} + +void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const geometry::VertexVector& vertices) { vertexBuffer->append(vertices); _positionView = gpu::BufferView(vertexBuffer, 0, @@ -63,7 +81,7 @@ void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, c vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, NORMAL_ELEMENT); } -void GeometryCache::ShapeData::setupIndices(gpu::BufferPointer& indexBuffer, const IndexVector& indices, const IndexVector& wireIndices) { +void GeometryCache::ShapeData::setupIndices(gpu::BufferPointer& indexBuffer, const geometry::IndexVector& indices, const geometry::IndexVector& wireIndices) { _indices = indexBuffer; if (!indices.empty()) { _indexOffset = indexBuffer->getSize() / SHAPE_INDEX_SIZE; @@ -112,101 +130,7 @@ void GeometryCache::ShapeData::drawWireInstances(gpu::Batch& batch, size_t count } } -const VertexVector& icosahedronVertices() { - static const float phi = (1.0f + sqrtf(5.0f)) / 2.0f; - static const float a = 0.5f; - static const float b = 1.0f / (2.0f * phi); - - static const VertexVector vertices{ // - vec3(0, b, -a), vec3(-b, a, 0), vec3(b, a, 0), // - vec3(0, b, a), vec3(b, a, 0), vec3(-b, a, 0), // - vec3(0, b, a), vec3(-a, 0, b), vec3(0, -b, a), // - vec3(0, b, a), vec3(0, -b, a), vec3(a, 0, b), // - vec3(0, b, -a), vec3(a, 0, -b), vec3(0, -b, -a),// - vec3(0, b, -a), vec3(0, -b, -a), vec3(-a, 0, -b), // - vec3(0, -b, a), vec3(-b, -a, 0), vec3(b, -a, 0), // - vec3(0, -b, -a), vec3(b, -a, 0), vec3(-b, -a, 0), // - vec3(-b, a, 0), vec3(-a, 0, -b), vec3(-a, 0, b), // - vec3(-b, -a, 0), vec3(-a, 0, b), vec3(-a, 0, -b), // - vec3(b, a, 0), vec3(a, 0, b), vec3(a, 0, -b), // - vec3(b, -a, 0), vec3(a, 0, -b), vec3(a, 0, b), // - vec3(0, b, a), vec3(-b, a, 0), vec3(-a, 0, b), // - vec3(0, b, a), vec3(a, 0, b), vec3(b, a, 0), // - vec3(0, b, -a), vec3(-a, 0, -b), vec3(-b, a, 0), // - vec3(0, b, -a), vec3(b, a, 0), vec3(a, 0, -b), // - vec3(0, -b, -a), vec3(-b, -a, 0), vec3(-a, 0, -b), // - vec3(0, -b, -a), vec3(a, 0, -b), vec3(b, -a, 0), // - vec3(0, -b, a), vec3(-a, 0, b), vec3(-b, -a, 0), // - vec3(0, -b, a), vec3(b, -a, 0), vec3(a, 0, b) - }; // - return vertices; -} - -const VertexVector& tetrahedronVertices() { - static const float a = 1.0f / sqrtf(2.0f); - static const auto A = vec3(0, 1, a); - static const auto B = vec3(0, -1, a); - static const auto C = vec3(1, 0, -a); - static const auto D = vec3(-1, 0, -a); - static const VertexVector vertices{ - A, B, C, - D, B, A, - C, D, A, - C, B, D, - }; - return vertices; -} - -static const size_t TESSELTATION_MULTIPLIER = 4; static const size_t ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT = 3; -static const size_t VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER = 2; - - -VertexVector tesselate(const VertexVector& startingTriangles, int count) { - VertexVector triangles = startingTriangles; - if (0 != (triangles.size() % 3)) { - throw std::runtime_error("Bad number of vertices for tesselation"); - } - - for (size_t i = 0; i < triangles.size(); ++i) { - triangles[i] = glm::normalize(triangles[i]); - } - - VertexVector newTriangles; - while (count) { - newTriangles.clear(); - // Tesselation takes one triangle and makes it into 4 triangles - // See https://en.wikipedia.org/wiki/Space-filling_tree#/media/File:Space_Filling_Tree_Tri_iter_1_2_3.png - newTriangles.reserve(triangles.size() * TESSELTATION_MULTIPLIER); - for (size_t i = 0; i < triangles.size(); i += VERTICES_PER_TRIANGLE) { - const vec3& a = triangles[i]; - const vec3& b = triangles[i + 1]; - const vec3& c = triangles[i + 2]; - vec3 ab = glm::normalize(a + b); - vec3 bc = glm::normalize(b + c); - vec3 ca = glm::normalize(c + a); - - newTriangles.push_back(a); - newTriangles.push_back(ab); - newTriangles.push_back(ca); - - newTriangles.push_back(b); - newTriangles.push_back(bc); - newTriangles.push_back(ab); - - newTriangles.push_back(c); - newTriangles.push_back(ca); - newTriangles.push_back(bc); - - newTriangles.push_back(ab); - newTriangles.push_back(bc); - newTriangles.push_back(ca); - } - triangles.swap(newTriangles); - --count; - } - return triangles; -} size_t GeometryCache::getShapeTriangleCount(Shape shape) { return _shapes[shape]._indexCount / VERTICES_PER_TRIANGLE; @@ -220,6 +144,182 @@ size_t GeometryCache::getCubeTriangleCount() { return getShapeTriangleCount(Cube); } +using IndexPair = uint64_t; +using IndexPairs = std::unordered_set; + +static IndexPair indexToken(geometry::Index a, geometry::Index b) { + if (a > b) { + std::swap(a, b); + } + return (((IndexPair)a) << 32) | ((IndexPair)b); +} + +template +void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { + using namespace geometry; + Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); + VertexVector vertices; + IndexVector solidIndices, wireIndices; + IndexPairs wireSeenIndices; + + size_t faceCount = shape.faces.size(); + size_t faceIndexCount = triangulatedFaceIndexCount(); + + vertices.reserve(N * faceCount * 2); + solidIndices.reserve(faceIndexCount * faceCount); + + for (size_t f = 0; f < faceCount; ++f) { + const Face& face = shape.faces[f]; + // Compute the face normal + vec3 faceNormal = shape.getFaceNormal(f); + + // Create the vertices for the face + for (Index i = 0; i < N; ++i) { + Index originalIndex = face[i]; + vertices.push_back(shape.vertices[originalIndex]); + vertices.push_back(faceNormal); + } + + // Create the wire indices for unseen edges + for (Index i = 0; i < N; ++i) { + Index a = i; + Index b = (i + 1) % N; + auto token = indexToken(face[a], face[b]); + if (0 == wireSeenIndices.count(token)) { + wireSeenIndices.insert(token); + wireIndices.push_back(a + baseVertex); + wireIndices.push_back(b + baseVertex); + } + } + + // Create the solid face indices + for (Index i = 0; i < N - 2; ++i) { + solidIndices.push_back(0 + baseVertex); + solidIndices.push_back(i + 1 + baseVertex); + solidIndices.push_back(i + 2 + baseVertex); + } + baseVertex += (Index)N; + } + + shapeData.setupVertices(vertexBuffer, vertices); + shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); +} + +template +void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { + using namespace geometry; + Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); + + VertexVector vertices; + vertices.reserve(shape.vertices.size() * 2); + for (const auto& vertex : shape.vertices) { + vertices.push_back(vertex); + vertices.push_back(vertex); + } + + IndexVector solidIndices, wireIndices; + IndexPairs wireSeenIndices; + + size_t faceCount = shape.faces.size(); + size_t faceIndexCount = triangulatedFaceIndexCount(); + + solidIndices.reserve(faceIndexCount * faceCount); + + for (size_t f = 0; f < faceCount; ++f) { + const Face& face = shape.faces[f]; + // Create the wire indices for unseen edges + for (Index i = 0; i < N; ++i) { + Index a = face[i]; + Index b = face[(i + 1) % N]; + auto token = indexToken(a, b); + if (0 == wireSeenIndices.count(token)) { + wireSeenIndices.insert(token); + wireIndices.push_back(a + baseVertex); + wireIndices.push_back(b + baseVertex); + } + } + + // Create the solid face indices + for (Index i = 0; i < N - 2; ++i) { + solidIndices.push_back(face[i] + baseVertex); + solidIndices.push_back(face[i + 1] + baseVertex); + solidIndices.push_back(face[i + 2] + baseVertex); + } + } + + shapeData.setupVertices(vertexBuffer, vertices); + shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); +} + +template +void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { + using namespace geometry; + Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); + VertexVector vertices; + IndexVector solidIndices, wireIndices; + + // Top and bottom faces + std::vector shape = polygon(); + for (const vec3& v : shape) { + vertices.push_back(vec3(v.x, 0.5f, v.z)); + vertices.push_back(vec3(0, 1, 0)); + } + for (const vec3& v : shape) { + vertices.push_back(vec3(v.x, -0.5f, v.z)); + vertices.push_back(vec3(0, -1, 0)); + } + for (uint32_t i = 2; i < N; ++i) { + solidIndices.push_back(baseVertex + 0); + solidIndices.push_back(baseVertex + i); + solidIndices.push_back(baseVertex + i - 1); + solidIndices.push_back(baseVertex + N); + solidIndices.push_back(baseVertex + i + N - 1); + solidIndices.push_back(baseVertex + i + N); + } + for (uint32_t i = 1; i <= N; ++i) { + wireIndices.push_back(baseVertex + (i % N)); + wireIndices.push_back(baseVertex + i - 1); + wireIndices.push_back(baseVertex + (i % N) + N); + wireIndices.push_back(baseVertex + (i - 1) + N); + } + + // Now do the sides + baseVertex += 2 * N; + + for (uint32_t i = 0; i < N; ++i) { + vec3 left = shape[i]; + vec3 right = shape[(i + 1) % N]; + vec3 normal = glm::normalize(left + right); + vec3 topLeft = vec3(left.x, 0.5f, left.z); + vec3 topRight = vec3(right.x, 0.5f, right.z); + vec3 bottomLeft = vec3(left.x, -0.5f, left.z); + vec3 bottomRight = vec3(right.x, -0.5f, right.z); + + vertices.push_back(topLeft); + vertices.push_back(normal); + vertices.push_back(bottomLeft); + vertices.push_back(normal); + vertices.push_back(topRight); + vertices.push_back(normal); + vertices.push_back(bottomRight); + vertices.push_back(normal); + + solidIndices.push_back(baseVertex + 0); + solidIndices.push_back(baseVertex + 2); + solidIndices.push_back(baseVertex + 1); + solidIndices.push_back(baseVertex + 1); + solidIndices.push_back(baseVertex + 2); + solidIndices.push_back(baseVertex + 3); + wireIndices.push_back(baseVertex + 0); + wireIndices.push_back(baseVertex + 1); + wireIndices.push_back(baseVertex + 3); + wireIndices.push_back(baseVertex + 2); + baseVertex += 4; + } + + shapeData.setupVertices(vertexBuffer, vertices); + shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); +} // FIXME solids need per-face vertices, but smooth shaded // components do not. Find a way to support using draw elements @@ -227,251 +327,52 @@ size_t GeometryCache::getCubeTriangleCount() { // Maybe special case cone and cylinder since they combine flat // and smooth shading void GeometryCache::buildShapes() { + using namespace geometry; auto vertexBuffer = std::make_shared(); auto indexBuffer = std::make_shared(); - size_t startingIndex = 0; - // Cube - startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; - { - ShapeData& shapeData = _shapes[Cube]; - VertexVector vertices; - // front - vertices.push_back(vec3(1, 1, 1)); - vertices.push_back(vec3(0, 0, 1)); - vertices.push_back(vec3(-1, 1, 1)); - vertices.push_back(vec3(0, 0, 1)); - vertices.push_back(vec3(-1, -1, 1)); - vertices.push_back(vec3(0, 0, 1)); - vertices.push_back(vec3(1, -1, 1)); - vertices.push_back(vec3(0, 0, 1)); - - // right - vertices.push_back(vec3(1, 1, 1)); - vertices.push_back(vec3(1, 0, 0)); - vertices.push_back(vec3(1, -1, 1)); - vertices.push_back(vec3(1, 0, 0)); - vertices.push_back(vec3(1, -1, -1)); - vertices.push_back(vec3(1, 0, 0)); - vertices.push_back(vec3(1, 1, -1)); - vertices.push_back(vec3(1, 0, 0)); - - // top - vertices.push_back(vec3(1, 1, 1)); - vertices.push_back(vec3(0, 1, 0)); - vertices.push_back(vec3(1, 1, -1)); - vertices.push_back(vec3(0, 1, 0)); - vertices.push_back(vec3(-1, 1, -1)); - vertices.push_back(vec3(0, 1, 0)); - vertices.push_back(vec3(-1, 1, 1)); - vertices.push_back(vec3(0, 1, 0)); - - // left - vertices.push_back(vec3(-1, 1, 1)); - vertices.push_back(vec3(-1, 0, 0)); - vertices.push_back(vec3(-1, 1, -1)); - vertices.push_back(vec3(-1, 0, 0)); - vertices.push_back(vec3(-1, -1, -1)); - vertices.push_back(vec3(-1, 0, 0)); - vertices.push_back(vec3(-1, -1, 1)); - vertices.push_back(vec3(-1, 0, 0)); - - // bottom - vertices.push_back(vec3(-1, -1, -1)); - vertices.push_back(vec3(0, -1, 0)); - vertices.push_back(vec3(1, -1, -1)); - vertices.push_back(vec3(0, -1, 0)); - vertices.push_back(vec3(1, -1, 1)); - vertices.push_back(vec3(0, -1, 0)); - vertices.push_back(vec3(-1, -1, 1)); - vertices.push_back(vec3(0, -1, 0)); - - // back - vertices.push_back(vec3(1, -1, -1)); - vertices.push_back(vec3(0, 0, -1)); - vertices.push_back(vec3(-1, -1, -1)); - vertices.push_back(vec3(0, 0, -1)); - vertices.push_back(vec3(-1, 1, -1)); - vertices.push_back(vec3(0, 0, -1)); - vertices.push_back(vec3(1, 1, -1)); - vertices.push_back(vec3(0, 0, -1)); - - static const size_t VERTEX_FORMAT_SIZE = 2; - static const size_t VERTEX_OFFSET = 0; - - for (size_t i = 0; i < vertices.size(); ++i) { - auto vertexIndex = i; - // Make a unit cube by having the vertices (at index N) - // while leaving the normals (at index N + 1) alone - if (VERTEX_OFFSET == vertexIndex % VERTEX_FORMAT_SIZE) { - vertices[vertexIndex] *= 0.5f; - } - } - shapeData.setupVertices(_shapeVertices, vertices); - - IndexVector indices{ - 0, 1, 2, 2, 3, 0, // front - 4, 5, 6, 6, 7, 4, // right - 8, 9, 10, 10, 11, 8, // top - 12, 13, 14, 14, 15, 12, // left - 16, 17, 18, 18, 19, 16, // bottom - 20, 21, 22, 22, 23, 20 // back - }; - for (auto& index : indices) { - index += (uint16_t)startingIndex; - } - - IndexVector wireIndices{ - 0, 1, 1, 2, 2, 3, 3, 0, // front - 20, 21, 21, 22, 22, 23, 23, 20, // back - 0, 23, 1, 22, 2, 21, 3, 20 // sides - }; - - for (size_t i = 0; i < wireIndices.size(); ++i) { - indices[i] += (uint16_t)startingIndex; - } - - shapeData.setupIndices(_shapeIndices, indices, wireIndices); - } - + setupFlatShape(_shapes[Cube], geometry::cube(), _shapeVertices, _shapeIndices); // Tetrahedron - startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; - { - ShapeData& shapeData = _shapes[Tetrahedron]; - size_t vertexCount = 4; - VertexVector vertices; - { - VertexVector originalVertices = tetrahedronVertices(); - vertexCount = originalVertices.size(); - vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER); - for (size_t i = 0; i < originalVertices.size(); i += VERTICES_PER_TRIANGLE) { - auto triangleStartIndex = i; - vec3 faceNormal; - for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - faceNormal += originalVertices[vertexIndex]; - } - faceNormal = glm::normalize(faceNormal); - for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - vertices.push_back(glm::normalize(originalVertices[vertexIndex])); - vertices.push_back(faceNormal); - } - } - } - shapeData.setupVertices(_shapeVertices, vertices); - - IndexVector indices; - for (size_t i = 0; i < vertexCount; i += VERTICES_PER_TRIANGLE) { - auto triangleStartIndex = i; - for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - indices.push_back((uint16_t)(vertexIndex + startingIndex)); - } - } - - IndexVector wireIndices{ - 0, 1, 1, 2, 2, 0, - 0, 3, 1, 3, 2, 3, - }; - - for (size_t i = 0; i < wireIndices.size(); ++i) { - wireIndices[i] += (uint16_t)startingIndex; - } - - shapeData.setupIndices(_shapeIndices, indices, wireIndices); - } + setupFlatShape(_shapes[Tetrahedron], geometry::tetrahedron(), _shapeVertices, _shapeIndices); + // Icosahedron + setupFlatShape(_shapes[Icosahedron], geometry::icosahedron(), _shapeVertices, _shapeIndices); + // Octahedron + setupFlatShape(_shapes[Octahedron], geometry::octahedron(), _shapeVertices, _shapeIndices); + // Dodecahedron + setupFlatShape(_shapes[Dodecahedron], geometry::dodecahedron(), _shapeVertices, _shapeIndices); // Sphere // FIXME this uses way more vertices than required. Should find a way to calculate the indices // using shared vertices for better vertex caching - startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; - { - ShapeData& shapeData = _shapes[Sphere]; - VertexVector vertices; - IndexVector indices; - { - VertexVector originalVertices = tesselate(icosahedronVertices(), ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT); - vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER); - for (size_t i = 0; i < originalVertices.size(); i += VERTICES_PER_TRIANGLE) { - auto triangleStartIndex = i; - for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - const auto& vertex = originalVertices[i + j]; - // Spheres use the same values for vertices and normals - vertices.push_back(vertex); - vertices.push_back(vertex); - indices.push_back((uint16_t)(vertexIndex + startingIndex)); - } - } - } - - shapeData.setupVertices(_shapeVertices, vertices); - // FIXME don't use solid indices for wire drawing. - shapeData.setupIndices(_shapeIndices, indices, indices); - } - - // Icosahedron - startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; - { - ShapeData& shapeData = _shapes[Icosahedron]; - - VertexVector vertices; - IndexVector indices; - { - const VertexVector& originalVertices = icosahedronVertices(); - vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER); - for (size_t i = 0; i < originalVertices.size(); i += 3) { - auto triangleStartIndex = i; - vec3 faceNormal; - for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - faceNormal += originalVertices[vertexIndex]; - } - faceNormal = glm::normalize(faceNormal); - for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - vertices.push_back(glm::normalize(originalVertices[vertexIndex])); - vertices.push_back(faceNormal); - indices.push_back((uint16_t)(vertexIndex + startingIndex)); - } - } - } - - shapeData.setupVertices(_shapeVertices, vertices); - // FIXME don't use solid indices for wire drawing. - shapeData.setupIndices(_shapeIndices, indices, indices); - } + auto sphere = geometry::tesselate(geometry::icosahedron(), ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT); + sphere.fitDimension(1.0f); + setupSmoothShape(_shapes[Sphere], sphere, _shapeVertices, _shapeIndices); // Line - startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; { + Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE); ShapeData& shapeData = _shapes[Line]; - shapeData.setupVertices(_shapeVertices, VertexVector{ + shapeData.setupVertices(_shapeVertices, VertexVector { vec3(-0.5, 0, 0), vec3(-0.5f, 0, 0), vec3(0.5f, 0, 0), vec3(0.5f, 0, 0) }); IndexVector wireIndices; // Only two indices - wireIndices.push_back(0 + (uint16_t)startingIndex); - wireIndices.push_back(1 + (uint16_t)startingIndex); - + wireIndices.push_back(0 + baseVertex); + wireIndices.push_back(1 + baseVertex); shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices); } // Not implememented yet: //Triangle, + extrudePolygon<3>(_shapes[Triangle], _shapeVertices, _shapeIndices); + //Hexagon, + extrudePolygon<6>(_shapes[Hexagon], _shapeVertices, _shapeIndices); + //Octagon, + extrudePolygon<8>(_shapes[Octagon], _shapeVertices, _shapeIndices); //Quad, //Circle, - //Octahetron, - //Dodecahedron, //Torus, //Cone, //Cylinder, @@ -497,30 +398,34 @@ gpu::Stream::FormatPointer& getInstancedSolidStreamFormat() { } render::ShapePipelinePointer GeometryCache::_simplePipeline; +render::ShapePipelinePointer GeometryCache::_simpleWirePipeline; GeometryCache::GeometryCache() : - _nextID(0) -{ +_nextID(0) { buildShapes(); GeometryCache::_simplePipeline = std::make_shared(getSimplePipeline(), nullptr, - [](const render::ShapePipeline&, gpu::Batch& batch) { - // Set the defaults needed for a simple program - batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, - DependencyManager::get()->getWhiteTexture()); - batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING, - DependencyManager::get()->getNormalFittingTexture()); - } - ); + [](const render::ShapePipeline&, gpu::Batch& batch) { + // Set the defaults needed for a simple program + batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, + DependencyManager::get()->getWhiteTexture()); + batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING, + DependencyManager::get()->getNormalFittingTexture()); + } + ); + GeometryCache::_simpleWirePipeline = + std::make_shared(getSimplePipeline(false, false, true, true), nullptr, + [](const render::ShapePipeline&, gpu::Batch& batch) {} + ); } GeometryCache::~GeometryCache() { - #ifdef WANT_DEBUG - qCDebug(renderutils) << "GeometryCache::~GeometryCache()... "; - qCDebug(renderutils) << " _registeredLine3DVBOs.size():" << _registeredLine3DVBOs.size(); - qCDebug(renderutils) << " _line3DVBOs.size():" << _line3DVBOs.size(); - qCDebug(renderutils) << " BatchItemDetails... population:" << GeometryCache::BatchItemDetails::population; - #endif //def WANT_DEBUG +#ifdef WANT_DEBUG + qCDebug(renderutils) << "GeometryCache::~GeometryCache()... "; + qCDebug(renderutils) << " _registeredLine3DVBOs.size():" << _registeredLine3DVBOs.size(); + qCDebug(renderutils) << " _line3DVBOs.size():" << _line3DVBOs.size(); + qCDebug(renderutils) << " BatchItemDetails... population:" << GeometryCache::BatchItemDetails::population; +#endif //def WANT_DEBUG } void setupBatchInstance(gpu::Batch& batch, gpu::BufferPointer colorBuffer) { @@ -567,9 +472,9 @@ void GeometryCache::renderWireSphere(gpu::Batch& batch) { } void GeometryCache::renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, - int majorRows, int majorCols, float majorEdge, - int minorRows, int minorCols, float minorEdge, - const glm::vec4& color, bool isLayered, int id) { + int majorRows, int majorCols, float majorEdge, + int minorRows, int minorCols, float minorEdge, + const glm::vec4& color, bool isLayered, int id) { static const glm::vec2 MIN_TEX_COORD(0.0f, 0.0f); static const glm::vec2 MAX_TEX_COORD(1.0f, 1.0f); @@ -611,14 +516,14 @@ void GeometryCache::renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, co renderQuad(batch, minCorner, maxCorner, MIN_TEX_COORD, MAX_TEX_COORD, color, id); } -void GeometryCache::updateVertices(int id, const QVector& points, const glm::vec4& color) { +void GeometryCache::updateVertices(int id, const QVector& points, const QVector& colors) { BatchItemDetails& details = _registeredVertices[id]; if (details.isCreated) { details.clear(); - #ifdef WANT_DEBUG - qCDebug(renderutils) << "updateVertices()... RELEASING REGISTERED"; - #endif // def WANT_DEBUG +#ifdef WANT_DEBUG + qCDebug(renderutils) << "updateVertices()... RELEASING REGISTERED"; +#endif // def WANT_DEBUG } const int FLOATS_PER_VERTEX = 2 + 3; // vertices + normals @@ -627,7 +532,7 @@ void GeometryCache::updateVertices(int id, const QVector& points, con details.isCreated = true; details.vertices = points.size(); details.vertexSize = FLOATS_PER_VERTEX; - + auto verticesBuffer = std::make_shared(); auto colorBuffer = std::make_shared(); auto streamFormat = std::make_shared(); @@ -648,11 +553,6 @@ void GeometryCache::updateVertices(int id, const QVector& points, con details.vertices = points.size(); details.vertexSize = FLOATS_PER_VERTEX; - int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); - float* vertexData = new float[details.vertices * FLOATS_PER_VERTEX]; float* vertex = vertexData; @@ -660,33 +560,46 @@ void GeometryCache::updateVertices(int id, const QVector& points, con int* colorDataAt = colorData; const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); - foreach (const glm::vec2& point, points) { + auto pointCount = points.size(); + auto colorCount = colors.size(); + int compactColor = 0; + for (auto i = 0; i < pointCount; ++i) { + const auto& point = points[i]; *(vertex++) = point.x; *(vertex++) = point.y; *(vertex++) = NORMAL.x; *(vertex++) = NORMAL.y; *(vertex++) = NORMAL.z; - + if (i < colorCount) { + const auto& color = colors[i]; + compactColor = ((int(color.x * 255.0f) & 0xFF)) | + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); + } *(colorDataAt++) = compactColor; } - details.verticesBuffer->append(sizeof(float) * FLOATS_PER_VERTEX * details.vertices, (gpu::Byte*) vertexData); details.colorBuffer->append(sizeof(int) * details.vertices, (gpu::Byte*) colorData); delete[] vertexData; delete[] colorData; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "new registered linestrip buffer made -- _registeredVertices.size():" << _registeredVertices.size(); - #endif +#ifdef WANT_DEBUG + qCDebug(renderutils) << "new registered linestrip buffer made -- _registeredVertices.size():" << _registeredVertices.size(); +#endif } -void GeometryCache::updateVertices(int id, const QVector& points, const glm::vec4& color) { +void GeometryCache::updateVertices(int id, const QVector& points, const glm::vec4& color) { + updateVertices(id, points, QVector({ color })); +} + +void GeometryCache::updateVertices(int id, const QVector& points, const QVector& colors) { BatchItemDetails& details = _registeredVertices[id]; if (details.isCreated) { details.clear(); - #ifdef WANT_DEBUG - qCDebug(renderutils) << "updateVertices()... RELEASING REGISTERED"; - #endif // def WANT_DEBUG +#ifdef WANT_DEBUG + qCDebug(renderutils) << "updateVertices()... RELEASING REGISTERED"; +#endif // def WANT_DEBUG } const int FLOATS_PER_VERTEX = 3 + 3; // vertices + normals @@ -695,7 +608,7 @@ void GeometryCache::updateVertices(int id, const QVector& points, con details.isCreated = true; details.vertices = points.size(); details.vertexSize = FLOATS_PER_VERTEX; - + auto verticesBuffer = std::make_shared(); auto colorBuffer = std::make_shared(); auto streamFormat = std::make_shared(); @@ -716,11 +629,8 @@ void GeometryCache::updateVertices(int id, const QVector& points, con details.vertices = points.size(); details.vertexSize = FLOATS_PER_VERTEX; - int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); - + // Default to white + int compactColor = 0xFFFFFFFF; float* vertexData = new float[details.vertices * FLOATS_PER_VERTEX]; float* vertex = vertexData; @@ -728,14 +638,23 @@ void GeometryCache::updateVertices(int id, const QVector& points, con int* colorDataAt = colorData; const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); - foreach (const glm::vec3& point, points) { + auto pointCount = points.size(); + auto colorCount = colors.size(); + for (auto i = 0; i < pointCount; ++i) { + const glm::vec3& point = points[i]; + if (i < colorCount) { + const glm::vec4& color = colors[i]; + compactColor = ((int(color.x * 255.0f) & 0xFF)) | + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); + } *(vertex++) = point.x; *(vertex++) = point.y; *(vertex++) = point.z; *(vertex++) = NORMAL.x; *(vertex++) = NORMAL.y; *(vertex++) = NORMAL.z; - *(colorDataAt++) = compactColor; } @@ -744,9 +663,13 @@ void GeometryCache::updateVertices(int id, const QVector& points, con delete[] vertexData; delete[] colorData; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "new registered linestrip buffer made -- _registeredVertices.size():" << _registeredVertices.size(); - #endif +#ifdef WANT_DEBUG + qCDebug(renderutils) << "new registered linestrip buffer made -- _registeredVertices.size():" << _registeredVertices.size(); +#endif +} + +void GeometryCache::updateVertices(int id, const QVector& points, const glm::vec4& color) { + updateVertices(id, points, QVector({ color })); } void GeometryCache::updateVertices(int id, const QVector& points, const QVector& texCoords, const glm::vec4& color) { @@ -754,9 +677,9 @@ void GeometryCache::updateVertices(int id, const QVector& points, con if (details.isCreated) { details.clear(); - #ifdef WANT_DEBUG - qCDebug(renderutils) << "updateVertices()... RELEASING REGISTERED"; - #endif // def WANT_DEBUG +#ifdef WANT_DEBUG + qCDebug(renderutils) << "updateVertices()... RELEASING REGISTERED"; +#endif // def WANT_DEBUG } const int FLOATS_PER_VERTEX = 3 + 3 + 2; // vertices + normals + tex coords @@ -767,7 +690,7 @@ void GeometryCache::updateVertices(int id, const QVector& points, con details.isCreated = true; details.vertices = points.size(); details.vertexSize = FLOATS_PER_VERTEX; - + auto verticesBuffer = std::make_shared(); auto colorBuffer = std::make_shared(); auto streamFormat = std::make_shared(); @@ -792,9 +715,9 @@ void GeometryCache::updateVertices(int id, const QVector& points, con details.vertexSize = FLOATS_PER_VERTEX; int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); float* vertexData = new float[details.vertices * FLOATS_PER_VERTEX]; float* vertex = vertexData; @@ -823,9 +746,9 @@ void GeometryCache::updateVertices(int id, const QVector& points, con delete[] vertexData; delete[] colorData; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "new registered linestrip buffer made -- _registeredVertices.size():" << _registeredVertices.size(); - #endif +#ifdef WANT_DEBUG + qCDebug(renderutils) << "new registered linestrip buffer made -- _registeredVertices.size():" << _registeredVertices.size(); +#endif } void GeometryCache::renderVertices(gpu::Batch& batch, gpu::Primitive primitiveType, int id) { @@ -847,27 +770,27 @@ void GeometryCache::renderBevelCornersRect(gpu::Batch& batch, int x, int y, int Vec3Pair& lastKey = _lastRegisteredBevelRects[id]; if (lastKey != key) { details.clear(); - _lastRegisteredBevelRects[id] = key; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderBevelCornersRect()... RELEASING REGISTERED"; - #endif // def WANT_DEBUG + _lastRegisteredBevelRects[id] = key; +#ifdef WANT_DEBUG + qCDebug(renderutils) << "renderBevelCornersRect()... RELEASING REGISTERED"; +#endif // def WANT_DEBUG } - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG else { qCDebug(renderutils) << "renderBevelCornersRect()... REUSING PREVIOUSLY REGISTERED"; } - #endif // def WANT_DEBUG +#endif // def WANT_DEBUG } if (!details.isCreated) { static const int FLOATS_PER_VERTEX = 2; // vertices static const int NUM_VERTICES = 8; static const int NUM_FLOATS = NUM_VERTICES * FLOATS_PER_VERTEX; - + details.isCreated = true; details.vertices = NUM_VERTICES; details.vertexSize = FLOATS_PER_VERTEX; - + auto verticesBuffer = std::make_shared(); auto colorBuffer = std::make_shared(); auto streamFormat = std::make_shared(); @@ -877,7 +800,7 @@ void GeometryCache::renderBevelCornersRect(gpu::Batch& batch, int x, int y, int details.colorBuffer = colorBuffer; details.streamFormat = streamFormat; details.stream = stream; - + details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ)); details.streamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); @@ -896,7 +819,7 @@ void GeometryCache::renderBevelCornersRect(gpu::Batch& batch, int x, int y, int // 2 8 // // \ / // // 4 ------ 6 // - + // 1 vertexBuffer[vertexPoint++] = x; vertexBuffer[vertexPoint++] = y + height - bevelDistance; @@ -921,13 +844,13 @@ void GeometryCache::renderBevelCornersRect(gpu::Batch& batch, int x, int y, int // 8 vertexBuffer[vertexPoint++] = x + width; vertexBuffer[vertexPoint++] = y + bevelDistance; - + int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); int colors[NUM_VERTICES] = { compactColor, compactColor, compactColor, compactColor, - compactColor, compactColor, compactColor, compactColor }; + compactColor, compactColor, compactColor, compactColor }; details.verticesBuffer->append(sizeof(vertexBuffer), (gpu::Byte*) vertexBuffer); @@ -949,16 +872,16 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co Vec4Pair & lastKey = _lastRegisteredQuad2D[id]; if (lastKey != key) { details.clear(); - _lastRegisteredQuad2D[id] = key; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderQuad() 2D ... RELEASING REGISTERED"; - #endif // def WANT_DEBUG + _lastRegisteredQuad2D[id] = key; +#ifdef WANT_DEBUG + qCDebug(renderutils) << "renderQuad() 2D ... RELEASING REGISTERED"; +#endif // def WANT_DEBUG } - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG else { qCDebug(renderutils) << "renderQuad() 2D ... REUSING PREVIOUSLY REGISTERED"; } - #endif // def WANT_DEBUG +#endif // def WANT_DEBUG } const int FLOATS_PER_VERTEX = 2 + 3; // vertices + normals @@ -971,7 +894,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co details.isCreated = true; details.vertices = VERTICES; details.vertexSize = FLOATS_PER_VERTEX; - + auto verticesBuffer = std::make_shared(); auto colorBuffer = std::make_shared(); auto streamFormat = std::make_shared(); @@ -981,7 +904,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co details.colorBuffer = colorBuffer; details.streamFormat = streamFormat; details.stream = stream; - + details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0); details.streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_NORMAL_OFFSET); details.streamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); @@ -991,7 +914,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); - float vertexBuffer[VERTICES * FLOATS_PER_VERTEX] = { + float vertexBuffer[VERTICES * FLOATS_PER_VERTEX] = { minCorner.x, minCorner.y, NORMAL.x, NORMAL.y, NORMAL.z, maxCorner.x, minCorner.y, NORMAL.x, NORMAL.y, NORMAL.z, minCorner.x, maxCorner.y, NORMAL.x, NORMAL.y, NORMAL.z, @@ -1000,9 +923,9 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co const int NUM_COLOR_SCALARS_PER_QUAD = 4; int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); int colors[NUM_COLOR_SCALARS_PER_QUAD] = { compactColor, compactColor, compactColor, compactColor }; details.verticesBuffer->append(sizeof(vertexBuffer), (gpu::Byte*) vertexBuffer); @@ -1024,13 +947,13 @@ void GeometryCache::renderUnitQuad(gpu::Batch& batch, const glm::vec4& color, in void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, - const glm::vec2& texCoordMinCorner, const glm::vec2& texCoordMaxCorner, - const glm::vec4& color, int id) { + const glm::vec2& texCoordMinCorner, const glm::vec2& texCoordMaxCorner, + const glm::vec4& color, int id) { bool registered = (id != UNKNOWN_ID); Vec4PairVec4 key(Vec4Pair(glm::vec4(minCorner.x, minCorner.y, maxCorner.x, maxCorner.y), - glm::vec4(texCoordMinCorner.x, texCoordMinCorner.y, texCoordMaxCorner.x, texCoordMaxCorner.y)), - color); + glm::vec4(texCoordMinCorner.x, texCoordMinCorner.y, texCoordMaxCorner.x, texCoordMaxCorner.y)), + color); BatchItemDetails& details = registered ? _registeredQuad2DTextures[id] : _quad2DTextures[key]; // if this is a registered quad, and we have buffers, then check to see if the geometry changed and rebuild if needed @@ -1038,16 +961,16 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co Vec4PairVec4& lastKey = _lastRegisteredQuad2DTexture[id]; if (lastKey != key) { details.clear(); - _lastRegisteredQuad2DTexture[id] = key; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderQuad() 2D+texture ... RELEASING REGISTERED"; - #endif // def WANT_DEBUG + _lastRegisteredQuad2DTexture[id] = key; +#ifdef WANT_DEBUG + qCDebug(renderutils) << "renderQuad() 2D+texture ... RELEASING REGISTERED"; +#endif // def WANT_DEBUG } - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG else { qCDebug(renderutils) << "renderQuad() 2D+texture ... REUSING PREVIOUSLY REGISTERED"; } - #endif // def WANT_DEBUG +#endif // def WANT_DEBUG } const int FLOATS_PER_VERTEX = 2 + 3 + 2; // vertices + normals + tex coords @@ -1062,7 +985,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co details.isCreated = true; details.vertices = VERTICES; details.vertexSize = FLOATS_PER_VERTEX; - + auto verticesBuffer = std::make_shared(); auto colorBuffer = std::make_shared(); @@ -1074,7 +997,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co details.streamFormat = streamFormat; details.stream = stream; - + // zzmp: fix the normal across all renderQuad details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0); details.streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_NORMAL_OFFSET); @@ -1086,7 +1009,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); - float vertexBuffer[VERTICES * FLOATS_PER_VERTEX] = { + float vertexBuffer[VERTICES * FLOATS_PER_VERTEX] = { minCorner.x, minCorner.y, NORMAL.x, NORMAL.y, NORMAL.z, texCoordMinCorner.x, texCoordMinCorner.y, maxCorner.x, minCorner.y, NORMAL.x, NORMAL.y, NORMAL.z, texCoordMaxCorner.x, texCoordMinCorner.y, minCorner.x, maxCorner.y, NORMAL.x, NORMAL.y, NORMAL.z, texCoordMinCorner.x, texCoordMaxCorner.y, @@ -1096,9 +1019,9 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co const int NUM_COLOR_SCALARS_PER_QUAD = 4; int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); int colors[NUM_COLOR_SCALARS_PER_QUAD] = { compactColor, compactColor, compactColor, compactColor }; details.verticesBuffer->append(sizeof(vertexBuffer), (gpu::Byte*) vertexBuffer); @@ -1120,16 +1043,16 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& minCorner, co Vec3PairVec4& lastKey = _lastRegisteredQuad3D[id]; if (lastKey != key) { details.clear(); - _lastRegisteredQuad3D[id] = key; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderQuad() 3D ... RELEASING REGISTERED"; - #endif // def WANT_DEBUG + _lastRegisteredQuad3D[id] = key; +#ifdef WANT_DEBUG + qCDebug(renderutils) << "renderQuad() 3D ... RELEASING REGISTERED"; +#endif // def WANT_DEBUG } - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG else { qCDebug(renderutils) << "renderQuad() 3D ... REUSING PREVIOUSLY REGISTERED"; } - #endif // def WANT_DEBUG +#endif // def WANT_DEBUG } const int FLOATS_PER_VERTEX = 3 + 3; // vertices + normals @@ -1142,7 +1065,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& minCorner, co details.isCreated = true; details.vertices = VERTICES; details.vertexSize = FLOATS_PER_VERTEX; - + auto verticesBuffer = std::make_shared(); auto colorBuffer = std::make_shared(); @@ -1154,7 +1077,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& minCorner, co details.streamFormat = streamFormat; details.stream = stream; - + details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); details.streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_NORMAL_OFFSET); details.streamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); @@ -1164,7 +1087,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& minCorner, co const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); - float vertexBuffer[VERTICES * FLOATS_PER_VERTEX] = { + float vertexBuffer[VERTICES * FLOATS_PER_VERTEX] = { minCorner.x, minCorner.y, minCorner.z, NORMAL.x, NORMAL.y, NORMAL.z, maxCorner.x, minCorner.y, minCorner.z, NORMAL.x, NORMAL.y, NORMAL.z, minCorner.x, maxCorner.y, maxCorner.z, NORMAL.x, NORMAL.y, NORMAL.z, @@ -1173,9 +1096,9 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& minCorner, co const int NUM_COLOR_SCALARS_PER_QUAD = 4; int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); int colors[NUM_COLOR_SCALARS_PER_QUAD] = { compactColor, compactColor, compactColor, compactColor }; details.verticesBuffer->append(sizeof(vertexBuffer), (gpu::Byte*) vertexBuffer); @@ -1187,28 +1110,28 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& minCorner, co batch.draw(gpu::TRIANGLE_STRIP, 4, 0); } -void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& topLeft, const glm::vec3& bottomLeft, - const glm::vec3& bottomRight, const glm::vec3& topRight, - const glm::vec2& texCoordTopLeft, const glm::vec2& texCoordBottomLeft, - const glm::vec2& texCoordBottomRight, const glm::vec2& texCoordTopRight, - const glm::vec4& color, int id) { +void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& topLeft, const glm::vec3& bottomLeft, + const glm::vec3& bottomRight, const glm::vec3& topRight, + const glm::vec2& texCoordTopLeft, const glm::vec2& texCoordBottomLeft, + const glm::vec2& texCoordBottomRight, const glm::vec2& texCoordTopRight, + const glm::vec4& color, int id) { + +#ifdef WANT_DEBUG + qCDebug(renderutils) << "renderQuad() vec3 + texture VBO..."; + qCDebug(renderutils) << " topLeft:" << topLeft; + qCDebug(renderutils) << " bottomLeft:" << bottomLeft; + qCDebug(renderutils) << " bottomRight:" << bottomRight; + qCDebug(renderutils) << " topRight:" << topRight; + qCDebug(renderutils) << " texCoordTopLeft:" << texCoordTopLeft; + qCDebug(renderutils) << " texCoordBottomRight:" << texCoordBottomRight; + qCDebug(renderutils) << " color:" << color; +#endif //def WANT_DEBUG - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderQuad() vec3 + texture VBO..."; - qCDebug(renderutils) << " topLeft:" << topLeft; - qCDebug(renderutils) << " bottomLeft:" << bottomLeft; - qCDebug(renderutils) << " bottomRight:" << bottomRight; - qCDebug(renderutils) << " topRight:" << topRight; - qCDebug(renderutils) << " texCoordTopLeft:" << texCoordTopLeft; - qCDebug(renderutils) << " texCoordBottomRight:" << texCoordBottomRight; - qCDebug(renderutils) << " color:" << color; - #endif //def WANT_DEBUG - bool registered = (id != UNKNOWN_ID); Vec3PairVec4Pair key(Vec3Pair(topLeft, bottomRight), - Vec4Pair(glm::vec4(texCoordTopLeft.x,texCoordTopLeft.y,texCoordBottomRight.x,texCoordBottomRight.y), - color)); - + Vec4Pair(glm::vec4(texCoordTopLeft.x, texCoordTopLeft.y, texCoordBottomRight.x, texCoordBottomRight.y), + color)); + BatchItemDetails& details = registered ? _registeredQuad3DTextures[id] : _quad3DTextures[key]; // if this is a registered quad, and we have buffers, then check to see if the geometry changed and rebuild if needed @@ -1217,15 +1140,15 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& topLeft, cons if (lastKey != key) { details.clear(); _lastRegisteredQuad3DTexture[id] = key; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderQuad() 3D+texture ... RELEASING REGISTERED"; - #endif // def WANT_DEBUG +#ifdef WANT_DEBUG + qCDebug(renderutils) << "renderQuad() 3D+texture ... RELEASING REGISTERED"; +#endif // def WANT_DEBUG } - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG else { qCDebug(renderutils) << "renderQuad() 3D+texture ... REUSING PREVIOUSLY REGISTERED"; } - #endif // def WANT_DEBUG +#endif // def WANT_DEBUG } const int FLOATS_PER_VERTEX = 3 + 3 + 2; // vertices + normals + tex coords @@ -1241,7 +1164,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& topLeft, cons details.isCreated = true; details.vertices = VERTICES; details.vertexSize = FLOATS_PER_VERTEX; // NOTE: this isn't used for BatchItemDetails maybe we can get rid of it - + auto verticesBuffer = std::make_shared(); auto colorBuffer = std::make_shared(); auto streamFormat = std::make_shared(); @@ -1251,7 +1174,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& topLeft, cons details.colorBuffer = colorBuffer; details.streamFormat = streamFormat; details.stream = stream; - + details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); details.streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_NORMAL_OFFSET); details.streamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), VERTEX_TEXCOORD_OFFSET); @@ -1271,9 +1194,9 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& topLeft, cons const int NUM_COLOR_SCALARS_PER_QUAD = 4; int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); int colors[NUM_COLOR_SCALARS_PER_QUAD] = { compactColor, compactColor, compactColor, compactColor }; details.verticesBuffer->append(sizeof(vertexBuffer), (gpu::Byte*) vertexBuffer); @@ -1286,7 +1209,7 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& topLeft, cons } void GeometryCache::renderDashedLine(gpu::Batch& batch, const glm::vec3& start, const glm::vec3& end, const glm::vec4& color, - const float dash_length, const float gap_length, int id) { + const float dash_length, const float gap_length, int id) { bool registered = (id != UNKNOWN_ID); Vec3PairVec2Pair key(Vec3Pair(start, end), Vec2Pair(glm::vec2(color.x, color.y), glm::vec2(color.z, color.w))); @@ -1297,18 +1220,18 @@ void GeometryCache::renderDashedLine(gpu::Batch& batch, const glm::vec3& start, if (_lastRegisteredDashedLines[id] != key) { details.clear(); _lastRegisteredDashedLines[id] = key; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderDashedLine()... RELEASING REGISTERED"; - #endif // def WANT_DEBUG +#ifdef WANT_DEBUG + qCDebug(renderutils) << "renderDashedLine()... RELEASING REGISTERED"; +#endif // def WANT_DEBUG } } if (!details.isCreated) { int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); // draw each line segment with appropriate gaps const float SEGMENT_LENGTH = dash_length + gap_length; @@ -1326,7 +1249,7 @@ void GeometryCache::renderDashedLine(gpu::Batch& batch, const glm::vec3& start, details.vertices = (segmentCountFloor + 1) * 2; details.vertexSize = FLOATS_PER_VERTEX; details.isCreated = true; - + auto verticesBuffer = std::make_shared(); auto colorBuffer = std::make_shared(); auto streamFormat = std::make_shared(); @@ -1392,13 +1315,13 @@ void GeometryCache::renderDashedLine(gpu::Batch& batch, const glm::vec3& start, delete[] vertexData; delete[] colorData; - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG if (registered) { qCDebug(renderutils) << "new registered dashed line buffer made -- _registeredVertices:" << _registeredDashedLines.size(); } else { qCDebug(renderutils) << "new dashed lines buffer made -- _dashedLines:" << _dashedLines.size(); } - #endif +#endif } batch.setInputFormat(details.streamFormat); @@ -1410,41 +1333,39 @@ void GeometryCache::renderDashedLine(gpu::Batch& batch, const glm::vec3& start, int GeometryCache::BatchItemDetails::population = 0; GeometryCache::BatchItemDetails::BatchItemDetails() : - verticesBuffer(NULL), - colorBuffer(NULL), - streamFormat(NULL), - stream(NULL), - vertices(0), - vertexSize(0), - isCreated(false) -{ +verticesBuffer(NULL), +colorBuffer(NULL), +streamFormat(NULL), +stream(NULL), +vertices(0), +vertexSize(0), +isCreated(false) { population++; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "BatchItemDetails()... population:" << population << "**********************************"; - #endif +#ifdef WANT_DEBUG + qCDebug(renderutils) << "BatchItemDetails()... population:" << population << "**********************************"; +#endif } GeometryCache::BatchItemDetails::BatchItemDetails(const GeometryCache::BatchItemDetails& other) : - verticesBuffer(other.verticesBuffer), - colorBuffer(other.colorBuffer), - streamFormat(other.streamFormat), - stream(other.stream), - vertices(other.vertices), - vertexSize(other.vertexSize), - isCreated(other.isCreated) -{ +verticesBuffer(other.verticesBuffer), +colorBuffer(other.colorBuffer), +streamFormat(other.streamFormat), +stream(other.stream), +vertices(other.vertices), +vertexSize(other.vertexSize), +isCreated(other.isCreated) { population++; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "BatchItemDetails()... population:" << population << "**********************************"; - #endif +#ifdef WANT_DEBUG + qCDebug(renderutils) << "BatchItemDetails()... population:" << population << "**********************************"; +#endif } GeometryCache::BatchItemDetails::~BatchItemDetails() { population--; - clear(); - #ifdef WANT_DEBUG - qCDebug(renderutils) << "~BatchItemDetails()... population:" << population << "**********************************"; - #endif + clear(); +#ifdef WANT_DEBUG + qCDebug(renderutils) << "~BatchItemDetails()... population:" << population << "**********************************"; +#endif } void GeometryCache::BatchItemDetails::clear() { @@ -1455,23 +1376,23 @@ void GeometryCache::BatchItemDetails::clear() { stream.reset(); } -void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2, - const glm::vec4& color1, const glm::vec4& color2, int id) { - +void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2, + const glm::vec4& color1, const glm::vec4& color2, int id) { + bool registered = (id != UNKNOWN_ID); Vec3Pair key(p1, p2); BatchItemDetails& details = registered ? _registeredLine3DVBOs[id] : _line3DVBOs[key]; int compactColor1 = ((int(color1.x * 255.0f) & 0xFF)) | - ((int(color1.y * 255.0f) & 0xFF) << 8) | - ((int(color1.z * 255.0f) & 0xFF) << 16) | - ((int(color1.w * 255.0f) & 0xFF) << 24); + ((int(color1.y * 255.0f) & 0xFF) << 8) | + ((int(color1.z * 255.0f) & 0xFF) << 16) | + ((int(color1.w * 255.0f) & 0xFF) << 24); int compactColor2 = ((int(color2.x * 255.0f) & 0xFF)) | - ((int(color2.y * 255.0f) & 0xFF) << 8) | - ((int(color2.z * 255.0f) & 0xFF) << 16) | - ((int(color2.w * 255.0f) & 0xFF) << 24); + ((int(color2.y * 255.0f) & 0xFF) << 8) | + ((int(color2.z * 255.0f) & 0xFF) << 16) | + ((int(color2.w * 255.0f) & 0xFF) << 24); // if this is a registered quad, and we have buffers, then check to see if the geometry changed and rebuild if needed @@ -1479,16 +1400,16 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm Vec3Pair& lastKey = _lastRegisteredLine3D[id]; if (lastKey != key) { details.clear(); - _lastRegisteredLine3D[id] = key; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderLine() 3D ... RELEASING REGISTERED line"; - #endif // def WANT_DEBUG + _lastRegisteredLine3D[id] = key; +#ifdef WANT_DEBUG + qCDebug(renderutils) << "renderLine() 3D ... RELEASING REGISTERED line"; +#endif // def WANT_DEBUG } - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG else { qCDebug(renderutils) << "renderLine() 3D ... REUSING PREVIOUSLY REGISTERED line"; } - #endif // def WANT_DEBUG +#endif // def WANT_DEBUG } const int FLOATS_PER_VERTEX = 3 + 3; // vertices + normals @@ -1500,7 +1421,7 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm details.isCreated = true; details.vertices = vertices; details.vertexSize = FLOATS_PER_VERTEX; - + auto verticesBuffer = std::make_shared(); auto colorBuffer = std::make_shared(); auto streamFormat = std::make_shared(); @@ -1510,7 +1431,7 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm details.colorBuffer = colorBuffer; details.streamFormat = streamFormat; details.stream = stream; - + details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); details.streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_NORMAL_OFFSET); details.streamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); @@ -1521,7 +1442,7 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm const glm::vec3 NORMAL(1.0f, 0.0f, 0.0f); float vertexBuffer[vertices * FLOATS_PER_VERTEX] = { p1.x, p1.y, p1.z, NORMAL.x, NORMAL.y, NORMAL.z, - p2.x, p2.y, p2.z, NORMAL.x, NORMAL.y, NORMAL.z}; + p2.x, p2.y, p2.z, NORMAL.x, NORMAL.y, NORMAL.z }; const int NUM_COLOR_SCALARS = 2; int colors[NUM_COLOR_SCALARS] = { compactColor1, compactColor2 }; @@ -1529,13 +1450,13 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm details.verticesBuffer->append(sizeof(vertexBuffer), (gpu::Byte*) vertexBuffer); details.colorBuffer->append(sizeof(colors), (gpu::Byte*) colors); - #ifdef WANT_DEBUG - if (id == UNKNOWN_ID) { - qCDebug(renderutils) << "new renderLine() 3D VBO made -- _line3DVBOs.size():" << _line3DVBOs.size(); - } else { - qCDebug(renderutils) << "new registered renderLine() 3D VBO made -- _registeredLine3DVBOs.size():" << _registeredLine3DVBOs.size(); - } - #endif +#ifdef WANT_DEBUG + if (id == UNKNOWN_ID) { + qCDebug(renderutils) << "new renderLine() 3D VBO made -- _line3DVBOs.size():" << _line3DVBOs.size(); + } else { + qCDebug(renderutils) << "new registered renderLine() 3D VBO made -- _registeredLine3DVBOs.size():" << _registeredLine3DVBOs.size(); + } +#endif } // this is what it takes to render a quad @@ -1544,23 +1465,23 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm batch.draw(gpu::LINES, 2, 0); } -void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm::vec2& p2, - const glm::vec4& color1, const glm::vec4& color2, int id) { - +void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm::vec2& p2, + const glm::vec4& color1, const glm::vec4& color2, int id) { + bool registered = (id != UNKNOWN_ID); Vec2Pair key(p1, p2); BatchItemDetails& details = registered ? _registeredLine2DVBOs[id] : _line2DVBOs[key]; int compactColor1 = ((int(color1.x * 255.0f) & 0xFF)) | - ((int(color1.y * 255.0f) & 0xFF) << 8) | - ((int(color1.z * 255.0f) & 0xFF) << 16) | - ((int(color1.w * 255.0f) & 0xFF) << 24); + ((int(color1.y * 255.0f) & 0xFF) << 8) | + ((int(color1.z * 255.0f) & 0xFF) << 16) | + ((int(color1.w * 255.0f) & 0xFF) << 24); int compactColor2 = ((int(color2.x * 255.0f) & 0xFF)) | - ((int(color2.y * 255.0f) & 0xFF) << 8) | - ((int(color2.z * 255.0f) & 0xFF) << 16) | - ((int(color2.w * 255.0f) & 0xFF) << 24); + ((int(color2.y * 255.0f) & 0xFF) << 8) | + ((int(color2.z * 255.0f) & 0xFF) << 16) | + ((int(color2.w * 255.0f) & 0xFF) << 24); // if this is a registered quad, and we have buffers, then check to see if the geometry changed and rebuild if needed @@ -1569,15 +1490,15 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm if (lastKey != key) { details.clear(); _lastRegisteredLine2D[id] = key; - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderLine() 2D ... RELEASING REGISTERED line"; - #endif // def WANT_DEBUG +#ifdef WANT_DEBUG + qCDebug(renderutils) << "renderLine() 2D ... RELEASING REGISTERED line"; +#endif // def WANT_DEBUG } - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG else { qCDebug(renderutils) << "renderLine() 2D ... REUSING PREVIOUSLY REGISTERED line"; } - #endif // def WANT_DEBUG +#endif // def WANT_DEBUG } const int FLOATS_PER_VERTEX = 2; @@ -1587,7 +1508,7 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm details.isCreated = true; details.vertices = vertices; details.vertexSize = FLOATS_PER_VERTEX; - + auto verticesBuffer = std::make_shared(); auto colorBuffer = std::make_shared(); auto streamFormat = std::make_shared(); @@ -1597,7 +1518,7 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm details.colorBuffer = colorBuffer; details.streamFormat = streamFormat; details.stream = stream; - + details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); details.streamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); @@ -1613,13 +1534,103 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm details.verticesBuffer->append(sizeof(vertexBuffer), (gpu::Byte*) vertexBuffer); details.colorBuffer->append(sizeof(colors), (gpu::Byte*) colors); - #ifdef WANT_DEBUG - if (id == UNKNOWN_ID) { - qCDebug(renderutils) << "new renderLine() 2D VBO made -- _line3DVBOs.size():" << _line2DVBOs.size(); - } else { - qCDebug(renderutils) << "new registered renderLine() 2D VBO made -- _registeredLine2DVBOs.size():" << _registeredLine2DVBOs.size(); - } - #endif +#ifdef WANT_DEBUG + if (id == UNKNOWN_ID) { + qCDebug(renderutils) << "new renderLine() 2D VBO made -- _line3DVBOs.size():" << _line2DVBOs.size(); + } else { + qCDebug(renderutils) << "new registered renderLine() 2D VBO made -- _registeredLine2DVBOs.size():" << _registeredLine2DVBOs.size(); + } +#endif + } + + // this is what it takes to render a quad + batch.setInputFormat(details.streamFormat); + batch.setInputStream(0, *details.stream); + batch.draw(gpu::LINES, 2, 0); +} + + +void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2, + const glm::vec4& color, float glowIntensity, float glowWidth, int id) { + if (glowIntensity <= 0) { + renderLine(batch, p1, p2, color, id); + return; + } + + // Compile the shaders + static std::once_flag once; + std::call_once(once, [&] { + auto state = std::make_shared(); + auto VS = gpu::Shader::createVertex(std::string(glowLine_vert)); + auto GS = gpu::Shader::createGeometry(std::string(glowLine_geom)); + auto PS = gpu::Shader::createPixel(std::string(glowLine_frag)); + auto program = gpu::Shader::createProgram(VS, GS, PS); + state->setCullMode(gpu::State::CULL_NONE); + state->setDepthTest(true, false, gpu::LESS_EQUAL); + 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); + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING)); + gpu::Shader::makeProgram(*program, slotBindings); + _glowLinePipeline = gpu::Pipeline::create(program, state); + }); + + batch.setPipeline(_glowLinePipeline); + + Vec3Pair key(p1, p2); + bool registered = (id != UNKNOWN_ID); + BatchItemDetails& details = registered ? _registeredLine3DVBOs[id] : _line3DVBOs[key]; + + int compactColor = ((int(color.x * 255.0f) & 0xFF)) | + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); + + // if this is a registered quad, and we have buffers, then check to see if the geometry changed and rebuild if needed + if (registered && details.isCreated) { + Vec3Pair& lastKey = _lastRegisteredLine3D[id]; + if (lastKey != key) { + details.clear(); + _lastRegisteredLine3D[id] = key; + } + } + + const int FLOATS_PER_VERTEX = 3 + 3; // vertices + normals + const int NUM_POS_COORDS = 3; + const int VERTEX_NORMAL_OFFSET = NUM_POS_COORDS * sizeof(float); + const int vertices = 2; + if (!details.isCreated) { + details.isCreated = true; + details.vertices = vertices; + details.vertexSize = FLOATS_PER_VERTEX; + + auto verticesBuffer = std::make_shared(); + auto colorBuffer = std::make_shared(); + auto streamFormat = std::make_shared(); + auto stream = std::make_shared(); + + details.verticesBuffer = verticesBuffer; + details.colorBuffer = colorBuffer; + details.streamFormat = streamFormat; + details.stream = stream; + + details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); + details.streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_NORMAL_OFFSET); + details.streamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); + + details.stream->addBuffer(details.verticesBuffer, 0, details.streamFormat->getChannels().at(0)._stride); + details.stream->addBuffer(details.colorBuffer, 0, details.streamFormat->getChannels().at(1)._stride); + + const glm::vec3 NORMAL(1.0f, 0.0f, 0.0f); + float vertexBuffer[vertices * FLOATS_PER_VERTEX] = { + p1.x, p1.y, p1.z, NORMAL.x, NORMAL.y, NORMAL.z, + p2.x, p2.y, p2.z, NORMAL.x, NORMAL.y, NORMAL.z }; + + const int NUM_COLOR_SCALARS = 2; + int colors[NUM_COLOR_SCALARS] = { compactColor, compactColor }; + details.verticesBuffer->append(sizeof(vertexBuffer), (gpu::Byte*) vertexBuffer); + details.colorBuffer->append(sizeof(colors), (gpu::Byte*) colors); } // this is what it takes to render a quad @@ -1715,7 +1726,7 @@ public: SimpleProgramKey(bool textured = false, bool culled = true, - bool unlit = false, bool depthBias = false) { + bool unlit = false, bool depthBias = false) { _flags = (textured ? IS_TEXTURED : 0) | (culled ? IS_CULLED : 0) | (unlit ? IS_UNLIT : 0) | (depthBias ? HAS_DEPTH_BIAS : 0); } @@ -1745,7 +1756,7 @@ void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool cul } gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool culled, bool unlit, bool depthBiased) { - SimpleProgramKey config{ textured, culled, unlit, depthBiased }; + SimpleProgramKey config { textured, culled, unlit, depthBiased }; // Compile the shaders static std::once_flag once; @@ -1753,10 +1764,10 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool culled auto VS = gpu::Shader::createVertex(std::string(simple_vert)); auto PS = gpu::Shader::createPixel(std::string(simple_textured_frag)); auto PSUnlit = gpu::Shader::createPixel(std::string(simple_textured_unlit_frag)); - + _simpleShader = gpu::Shader::createProgram(VS, PS); _unlitShader = gpu::Shader::createProgram(VS, PSUnlit); - + gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING)); gpu::Shader::makeProgram(*_simpleShader, slotBindings); @@ -1793,18 +1804,18 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool culled uint32_t toCompactColor(const glm::vec4& color) { uint32_t compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); return compactColor; } static const size_t INSTANCE_COLOR_BUFFER = 0; -void renderInstances(const std::string& name, gpu::Batch& batch, const glm::vec4& color, bool isWire, - const render::ShapePipelinePointer& pipeline, GeometryCache::Shape shape) { +void renderInstances(gpu::Batch& batch, const glm::vec4& color, bool isWire, + const render::ShapePipelinePointer& pipeline, GeometryCache::Shape shape) { // Add pipeline to name - std::string instanceName = name + std::to_string(std::hash()(pipeline)); + std::string instanceName = (isWire ? "wire_shapes_" : "solid_shapes_") + std::to_string(shape) + "_" + std::to_string(std::hash()(pipeline)); // Add color to named buffer { @@ -1826,14 +1837,21 @@ void renderInstances(const std::string& name, gpu::Batch& batch, const glm::vec4 }); } +void GeometryCache::renderSolidShapeInstance(gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { + renderInstances(batch, color, false, pipeline, shape); +} + +void GeometryCache::renderWireShapeInstance(gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { + renderInstances(batch, color, true, pipeline, shape); +} + + void GeometryCache::renderSolidSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { - static const std::string INSTANCE_NAME = __FUNCTION__; - renderInstances(INSTANCE_NAME, batch, color, false, pipeline, GeometryCache::Sphere); + renderInstances(batch, color, false, pipeline, GeometryCache::Sphere); } void GeometryCache::renderWireSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { - static const std::string INSTANCE_NAME = __FUNCTION__; - renderInstances(INSTANCE_NAME, batch, color, true, pipeline, GeometryCache::Sphere); + renderInstances(batch, color, true, pipeline, GeometryCache::Sphere); } // Enable this in a debug build to cause 'box' entities to iterate through all the @@ -1841,12 +1859,10 @@ void GeometryCache::renderWireSphereInstance(gpu::Batch& batch, const glm::vec4& //#define DEBUG_SHAPES void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { - static const std::string INSTANCE_NAME = __FUNCTION__; - #ifdef DEBUG_SHAPES static auto startTime = usecTimestampNow(); renderInstances(INSTANCE_NAME, batch, color, pipeline, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { - + auto usecs = usecTimestampNow(); usecs -= startTime; auto msecs = usecs / USECS_PER_MSEC; @@ -1854,7 +1870,7 @@ void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4& seconds /= MSECS_PER_SECOND; float fractionalSeconds = seconds - floor(seconds); int shapeIndex = (int)seconds; - + // Every second we flip to the next shape. static const int SHAPE_COUNT = 5; GeometryCache::Shape shapes[SHAPE_COUNT] = { @@ -1864,10 +1880,10 @@ void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4& GeometryCache::Icosahedron, GeometryCache::Line, }; - + shapeIndex %= SHAPE_COUNT; GeometryCache::Shape shape = shapes[shapeIndex]; - + // For the first half second for a given shape, show the wireframe, for the second half, show the solid. if (fractionalSeconds > 0.5f) { renderInstances(INSTANCE_NAME, batch, color, true, pipeline, shape); @@ -1876,11 +1892,11 @@ void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4& } }); #else - renderInstances(INSTANCE_NAME, batch, color, false, pipeline, GeometryCache::Cube); + renderInstances(batch, color, false, pipeline, GeometryCache::Cube); #endif } void GeometryCache::renderWireCubeInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { static const std::string INSTANCE_NAME = __FUNCTION__; - renderInstances(INSTANCE_NAME, batch, color, true, pipeline, GeometryCache::Cube); + renderInstances(batch, color, true, pipeline, GeometryCache::Cube); } diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index c4531aa102..bab0942672 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -21,6 +21,8 @@ #include +#include + #include #include @@ -121,9 +123,6 @@ inline uint qHash(const Vec4PairVec4Pair& v, uint seed) { seed); } -using VertexVector = std::vector; -using IndexVector = std::vector; - /// Stores cached geometry. class GeometryCache : public Dependency { SINGLETON_DEPENDENCY @@ -133,17 +132,18 @@ public: Line, Triangle, Quad, + Hexagon, + Octagon, Circle, Cube, Sphere, Tetrahedron, - Octahetron, + Octahedron, Dodecahedron, Icosahedron, - Torus, - Cone, - Cylinder, - + Torus, // not yet implemented + Cone, // not yet implemented + Cylinder, // not yet implemented NUM_SHAPES, }; @@ -158,11 +158,26 @@ public: gpu::PipelinePointer getSimplePipeline(bool textured = false, bool culled = true, bool unlit = false, bool depthBias = false); render::ShapePipelinePointer getShapePipeline() { return GeometryCache::_simplePipeline; } - + render::ShapePipelinePointer getWireShapePipeline() { return GeometryCache::_simpleWirePipeline; } + // Static (instanced) geometry void renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer); void renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer); + void renderSolidShapeInstance(gpu::Batch& batch, Shape shape, const glm::vec4& color = glm::vec4(1), + const render::ShapePipelinePointer& pipeline = _simplePipeline); + void renderSolidShapeInstance(gpu::Batch& batch, Shape shape, const glm::vec3& color, + const render::ShapePipelinePointer& pipeline = _simplePipeline) { + renderSolidShapeInstance(batch, shape, glm::vec4(color, 1.0f), pipeline); + } + + void renderWireShapeInstance(gpu::Batch& batch, Shape shape, const glm::vec4& color = glm::vec4(1), + const render::ShapePipelinePointer& pipeline = _simplePipeline); + void renderWireShapeInstance(gpu::Batch& batch, Shape shape, const glm::vec3& color, + const render::ShapePipelinePointer& pipeline = _simplePipeline) { + renderWireShapeInstance(batch, shape, glm::vec4(color, 1.0f), pipeline); + } + void renderSolidSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline = _simplePipeline); void renderSolidSphereInstance(gpu::Batch& batch, const glm::vec3& color, @@ -173,7 +188,7 @@ public: void renderWireSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline = _simplePipeline); void renderWireSphereInstance(gpu::Batch& batch, const glm::vec3& color, - const render::ShapePipelinePointer& pipeline = _simplePipeline) { + const render::ShapePipelinePointer& pipeline = _simpleWirePipeline) { renderWireSphereInstance(batch, glm::vec4(color, 1.0f), pipeline); } @@ -187,7 +202,7 @@ public: void renderWireCubeInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline = _simplePipeline); void renderWireCubeInstance(gpu::Batch& batch, const glm::vec3& color, - const render::ShapePipelinePointer& pipeline = _simplePipeline) { + const render::ShapePipelinePointer& pipeline = _simpleWirePipeline) { renderWireCubeInstance(batch, glm::vec4(color, 1.0f), pipeline); } @@ -252,6 +267,9 @@ public: void renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2, const glm::vec4& color1, const glm::vec4& color2, int id = UNKNOWN_ID); + void renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2, + const glm::vec4& color, float glowIntensity = 1.0f, float glowWidth = 0.05f, int id = UNKNOWN_ID); + void renderDashedLine(gpu::Batch& batch, const glm::vec3& start, const glm::vec3& end, const glm::vec4& color, int id = UNKNOWN_ID) { renderDashedLine(batch, start, end, color, 0.05f, 0.025f, id); } @@ -273,7 +291,9 @@ public: const glm::vec4& color1, const glm::vec4& color2, int id = UNKNOWN_ID); void updateVertices(int id, const QVector& points, const glm::vec4& color); + void updateVertices(int id, const QVector& points, const QVector& colors); void updateVertices(int id, const QVector& points, const glm::vec4& color); + void updateVertices(int id, const QVector& points, const QVector& colors); void updateVertices(int id, const QVector& points, const QVector& texCoords, const glm::vec4& color); void renderVertices(gpu::Batch& batch, gpu::Primitive primitiveType, int id); @@ -290,8 +310,8 @@ public: gpu::BufferView _normalView; gpu::BufferPointer _indices; - void setupVertices(gpu::BufferPointer& vertexBuffer, const VertexVector& vertices); - void setupIndices(gpu::BufferPointer& indexBuffer, const IndexVector& indices, const IndexVector& wireIndices); + void setupVertices(gpu::BufferPointer& vertexBuffer, const geometry::VertexVector& vertices); + void setupIndices(gpu::BufferPointer& indexBuffer, const geometry::IndexVector& indices, const geometry::IndexVector& wireIndices); void setupBatch(gpu::Batch& batch) const; void draw(gpu::Batch& batch) const; void drawWire(gpu::Batch& batch) const; @@ -350,7 +370,7 @@ private: QHash _coneVBOs; - int _nextID{ 0 }; + int _nextID{ 1 }; QHash _lastRegisteredQuad3DTexture; QHash _quad3DTextures; @@ -390,11 +410,11 @@ private: QHash _gridBuffers; QHash _registeredGridBuffers; - QHash > _networkGeometry; - gpu::ShaderPointer _simpleShader; gpu::ShaderPointer _unlitShader; static render::ShapePipelinePointer _simplePipeline; + static render::ShapePipelinePointer _simpleWirePipeline; + gpu::PipelinePointer _glowLinePipeline; QHash _simplePrograms; }; diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh new file mode 100644 index 0000000000..ec665e6deb --- /dev/null +++ b/libraries/render-utils/src/LightAmbient.slh @@ -0,0 +1,115 @@ +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 7/5/16. +// Copyright 2016 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 +// + + + +<@func declareSkyboxMap()@> +// declareSkyboxMap +uniform samplerCube skyboxMap; + +vec4 evalSkyboxLight(vec3 direction, float lod) { + // textureQueryLevels is not available until #430, so we require explicit lod + // float mipmapLevel = lod * textureQueryLevels(skyboxMap); + return textureLod(skyboxMap, direction, lod); +} +<@endfunc@> + +<@func declareEvalAmbientSpecularIrradiance(supportAmbientSphere, supportAmbientMap, supportIfAmbientMapElseAmbientSphere)@> + +vec3 fresnelSchlickAmbient(vec3 fresnelColor, vec3 lightDir, vec3 halfDir, float gloss) { + return fresnelColor + (max(vec3(gloss), fresnelColor) - fresnelColor) * pow(1.0 - clamp(dot(lightDir, halfDir), 0.0, 1.0), 5); +} + +<@if supportAmbientMap@> +<$declareSkyboxMap()$> +<@endif@> + +vec3 evalAmbientSpecularIrradiance(Light light, vec3 fragEyeDir, vec3 fragNormal, float roughness, vec3 fresnel) { + vec3 direction = -reflect(fragEyeDir, fragNormal); + vec3 ambientFresnel = fresnelSchlickAmbient(fresnel, fragEyeDir, fragNormal, 1 - roughness); + vec3 specularLight; + <@if supportIfAmbientMapElseAmbientSphere@> + if (getLightHasAmbientMap(light)) + <@endif@> + <@if supportAmbientMap@> + { + float levels = getLightAmbientMapNumMips(light); + float lod = min(floor((roughness)* levels), levels); + specularLight = evalSkyboxLight(direction, lod).xyz; + } + <@endif@> + <@if supportIfAmbientMapElseAmbientSphere@> + else + <@endif@> + <@if supportAmbientSphere@> + { + specularLight = evalSphericalLight(getLightAmbientSphere(light), direction).xyz; + } + <@endif@> + + return specularLight * ambientFresnel; +} +<@endfunc@> + +<@func declareLightingAmbient(supportAmbientSphere, supportAmbientMap, supportIfAmbientMapElseAmbientSphere, supportScattering)@> + +<$declareEvalAmbientSpecularIrradiance($supportAmbientSphere$, $supportAmbientMap$, $supportIfAmbientMapElseAmbientSphere$)$> + +<@if supportScattering@> +float curvatureAO(in float k) { + return 1.0f - (0.0022f * k * k) + (0.0776f * k) + 0.7369; +} +<@endif@> + +void evalLightingAmbient(out vec3 diffuse, out vec3 specular, Light light, vec3 eyeDir, vec3 normal, + float roughness, float metallic, vec3 fresnel, vec3 albedo, float obscurance +<@if supportScattering@> + , float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature +<@endif@> + ) { + + + // Diffuse from ambient + diffuse = (1 - metallic) * evalSphericalLight(getLightAmbientSphere(light), normal).xyz; + + // Specular highlight from ambient + specular = evalAmbientSpecularIrradiance(light, eyeDir, normal, roughness, fresnel) * obscurance * getLightAmbientIntensity(light); + + +<@if supportScattering@> + float ambientOcclusion = curvatureAO(lowNormalCurvature.w * 20.0f) * 0.5f; + float ambientOcclusionHF = curvatureAO(midNormalCurvature.w * 8.0f) * 0.5f; + ambientOcclusion = min(ambientOcclusion, ambientOcclusionHF); + + obscurance = min(obscurance, ambientOcclusion); + + if (scattering * isScatteringEnabled() > 0.0) { + + // Diffuse from ambient + diffuse = evalSphericalLight(getLightAmbientSphere(light), lowNormalCurvature.xyz).xyz; + + specular = vec3(0.0); + } +<@endif@> + + if (!(isObscuranceEnabled() > 0.0)) { + obscurance = 1.0; + } + + float lightEnergy = obscurance * getLightAmbientIntensity(light); + + if (isAlbedoEnabled() > 0.0) { + diffuse *= albedo; + } + + diffuse *= lightEnergy * isDiffuseEnabled() * isAmbientEnabled(); + specular *= lightEnergy * isSpecularEnabled() * isAmbientEnabled(); +} + +<@endfunc@> diff --git a/libraries/render-utils/src/LightDirectional.slh b/libraries/render-utils/src/LightDirectional.slh new file mode 100644 index 0000000000..86eb130491 --- /dev/null +++ b/libraries/render-utils/src/LightDirectional.slh @@ -0,0 +1,36 @@ +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 7/5/16. +// Copyright 2016 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 +// + + + +<@func declareLightingDirectional(supportScattering)@> + +void evalLightingDirectional(out vec3 diffuse, out vec3 specular, Light light, + vec3 eyeDir, vec3 normal, float roughness, + float metallic, vec3 fresnel, vec3 albedo, float shadow +<@if supportScattering@> + , float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature +<@endif@> + ) { + + // Attenuation + vec3 lightEnergy = shadow * getLightColor(light) * getLightIntensity(light); + + evalFragShading(diffuse, specular, normal, -getLightDirection(light), eyeDir, metallic, fresnel, roughness, albedo +<@if supportScattering@> + ,scattering, midNormalCurvature, lowNormalCurvature +<@endif@> + ); + + diffuse *= lightEnergy * isDiffuseEnabled() * isDirectionalEnabled(); + specular *= lightEnergy * isSpecularEnabled() * isDirectionalEnabled(); +} + +<@endfunc@> + diff --git a/libraries/render-utils/src/LightPoint.slh b/libraries/render-utils/src/LightPoint.slh new file mode 100644 index 0000000000..5c9e66dd24 --- /dev/null +++ b/libraries/render-utils/src/LightPoint.slh @@ -0,0 +1,52 @@ +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 7/5/16. +// Copyright 2016 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 +// + + + +<@func declareLightingPoint(supportScattering)@> + +void evalLightingPoint(out vec3 diffuse, out vec3 specular, Light light, + vec3 fragLightVec, vec3 fragEyeDir, vec3 normal, float roughness, + float metallic, vec3 fresnel, vec3 albedo, float shadow +<@if supportScattering@> + , float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature +<@endif@> + ) { + + // Allright we re valid in the volume + float fragLightDistance = length(fragLightVec); + vec3 fragLightDir = fragLightVec / fragLightDistance; + + // Eval attenuation + float radialAttenuation = evalLightAttenuation(light, fragLightDistance); + vec3 lightEnergy = radialAttenuation * shadow * getLightColor(light) * getLightIntensity(light); + + // Eval shading + evalFragShading(diffuse, specular, normal, fragLightDir, fragEyeDir, metallic, fresnel, roughness, albedo +<@if supportScattering@> + ,scattering, midNormalCurvature, lowNormalCurvature +<@endif@> + ); + + diffuse *= lightEnergy * isDiffuseEnabled() * isPointEnabled(); + specular *= lightEnergy * isSpecularEnabled() * isPointEnabled(); + + if (isShowLightContour() > 0.0) { + // Show edge + float edge = abs(2.0 * ((getLightRadius(light) - fragLightDistance) / (0.1)) - 1.0); + if (edge < 1) { + float edgeCoord = exp2(-8.0*edge*edge); + diffuse = vec3(edgeCoord * edgeCoord * getLightShowContour(light) * getLightColor(light)); + } + } +} + +<@endfunc@> + + diff --git a/libraries/render-utils/src/LightSpot.slh b/libraries/render-utils/src/LightSpot.slh new file mode 100644 index 0000000000..8a17a5ef4d --- /dev/null +++ b/libraries/render-utils/src/LightSpot.slh @@ -0,0 +1,56 @@ +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 7/5/16. +// Copyright 2016 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 +// + + + +<@func declareLightingSpot(supportScattering)@> + +void evalLightingSpot(out vec3 diffuse, out vec3 specular, Light light, + vec4 fragLightDirLen, float cosSpotAngle, vec3 fragEyeDir, vec3 normal, float roughness, + float metallic, vec3 fresnel, vec3 albedo, float shadow +<@if supportScattering@> + , float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature +<@endif@> + ) { + + // Allright we re valid in the volume + float fragLightDistance = fragLightDirLen.w; + vec3 fragLightDir = fragLightDirLen.xyz; + + // Eval attenuation + float radialAttenuation = evalLightAttenuation(light, fragLightDistance); + float angularAttenuation = evalLightSpotAttenuation(light, cosSpotAngle); + vec3 lightEnergy = angularAttenuation * radialAttenuation * shadow * getLightColor(light) * getLightIntensity(light); + + // Eval shading + evalFragShading(diffuse, specular, normal, fragLightDir, fragEyeDir, metallic, fresnel, roughness, albedo +<@if supportScattering@> + ,scattering, midNormalCurvature, lowNormalCurvature +<@endif@> + ); + + diffuse *= lightEnergy * isDiffuseEnabled() * isSpotEnabled(); + specular *= lightEnergy * isSpecularEnabled() * isSpotEnabled(); + + if (isShowLightContour() > 0.0) { + // Show edges + float edgeDistR = (getLightRadius(light) - fragLightDistance); + float edgeDistS = dot(fragLightDistance * vec2(cosSpotAngle, sqrt(1.0 - cosSpotAngle * cosSpotAngle)), -getLightSpotOutsideNormal2(light)); + float edgeDist = min(edgeDistR, edgeDistS); + float edge = abs(2.0 * (edgeDist / (0.1)) - 1.0); + if (edge < 1) { + float edgeCoord = exp2(-8.0*edge*edge); + diffuse = vec3(edgeCoord * edgeCoord * getLightColor(light)); + } + } +} + +<@endfunc@> + + diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index fc6c3ff514..f1726feca6 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -88,8 +88,9 @@ const glm::mat4& LightStage::Shadow::getProjection() const { } const LightStage::LightPointer LightStage::addLight(model::LightPointer light) { - Shadow stageShadow{light}; - LightPointer stageLight = std::make_shared(std::move(stageShadow)); + // Shadow stageShadow{light}; + LightPointer stageLight = std::make_shared(Shadow(light)); + stageLight->light = light; lights.push_back(stageLight); return stageLight; } diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 9ed9789965..76d9a3b268 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -52,6 +52,8 @@ public: glm::float32 scale = 1 / MAP_SIZE; }; UniformBufferView _schemaBuffer = nullptr; + + friend class Light; }; using ShadowPointer = std::shared_ptr; diff --git a/libraries/render-utils/src/LightingModel.cpp b/libraries/render-utils/src/LightingModel.cpp new file mode 100644 index 0000000000..5a251fc5e9 --- /dev/null +++ b/libraries/render-utils/src/LightingModel.cpp @@ -0,0 +1,164 @@ +// +// LightingModel.cpp +// libraries/render-utils/src/ +// +// Created by Sam Gateau 7/1/2016. +// Copyright 2016 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 "LightingModel.h" + +LightingModel::LightingModel() { + Parameters parameters; + _parametersBuffer = gpu::BufferView(std::make_shared(sizeof(Parameters), (const gpu::Byte*) ¶meters)); +} + +void LightingModel::setUnlit(bool enable) { + if (enable != isUnlitEnabled()) { + _parametersBuffer.edit().enableUnlit = (float) enable; + } +} +bool LightingModel::isUnlitEnabled() const { + return (bool)_parametersBuffer.get().enableUnlit; +} + +void LightingModel::setEmissive(bool enable) { + if (enable != isEmissiveEnabled()) { + _parametersBuffer.edit().enableEmissive = (float)enable; + } +} +bool LightingModel::isEmissiveEnabled() const { + return (bool)_parametersBuffer.get().enableEmissive; +} +void LightingModel::setLightmap(bool enable) { + if (enable != isLightmapEnabled()) { + _parametersBuffer.edit().enableLightmap = (float)enable; + } +} +bool LightingModel::isLightmapEnabled() const { + return (bool)_parametersBuffer.get().enableLightmap; +} + +void LightingModel::setBackground(bool enable) { + if (enable != isBackgroundEnabled()) { + _parametersBuffer.edit().enableBackground = (float)enable; + } +} +bool LightingModel::isBackgroundEnabled() const { + return (bool)_parametersBuffer.get().enableBackground; +} +void LightingModel::setObscurance(bool enable) { + if (enable != isObscuranceEnabled()) { + _parametersBuffer.edit().enableObscurance = (float)enable; + } +} +bool LightingModel::isObscuranceEnabled() const { + return (bool)_parametersBuffer.get().enableObscurance; +} + +void LightingModel::setScattering(bool enable) { + if (enable != isScatteringEnabled()) { + _parametersBuffer.edit().enableScattering = (float)enable; + } +} +bool LightingModel::isScatteringEnabled() const { + return (bool)_parametersBuffer.get().enableScattering; +} + +void LightingModel::setDiffuse(bool enable) { + if (enable != isDiffuseEnabled()) { + _parametersBuffer.edit().enableDiffuse = (float)enable; + } +} +bool LightingModel::isDiffuseEnabled() const { + return (bool)_parametersBuffer.get().enableDiffuse; +} +void LightingModel::setSpecular(bool enable) { + if (enable != isSpecularEnabled()) { + _parametersBuffer.edit().enableSpecular = (float)enable; + } +} +bool LightingModel::isSpecularEnabled() const { + return (bool)_parametersBuffer.get().enableSpecular; +} +void LightingModel::setAlbedo(bool enable) { + if (enable != isAlbedoEnabled()) { + _parametersBuffer.edit().enableAlbedo = (float)enable; + } +} +bool LightingModel::isAlbedoEnabled() const { + return (bool)_parametersBuffer.get().enableAlbedo; +} + +void LightingModel::setAmbientLight(bool enable) { + if (enable != isAmbientLightEnabled()) { + _parametersBuffer.edit().enableAmbientLight = (float)enable; + } +} +bool LightingModel::isAmbientLightEnabled() const { + return (bool)_parametersBuffer.get().enableAmbientLight; +} +void LightingModel::setDirectionalLight(bool enable) { + if (enable != isDirectionalLightEnabled()) { + _parametersBuffer.edit().enableDirectionalLight = (float)enable; + } +} +bool LightingModel::isDirectionalLightEnabled() const { + return (bool)_parametersBuffer.get().enableDirectionalLight; +} +void LightingModel::setPointLight(bool enable) { + if (enable != isPointLightEnabled()) { + _parametersBuffer.edit().enablePointLight = (float)enable; + } +} +bool LightingModel::isPointLightEnabled() const { + return (bool)_parametersBuffer.get().enablePointLight; +} +void LightingModel::setSpotLight(bool enable) { + if (enable != isSpotLightEnabled()) { + _parametersBuffer.edit().enableSpotLight = (float)enable; + } +} +bool LightingModel::isSpotLightEnabled() const { + return (bool)_parametersBuffer.get().enableSpotLight; +} +void LightingModel::setShowLightContour(bool enable) { + if (enable != isShowLightContourEnabled()) { + _parametersBuffer.edit().showLightContour = (float)enable; + } +} +bool LightingModel::isShowLightContourEnabled() const { + return (bool)_parametersBuffer.get().showLightContour; +} + +MakeLightingModel::MakeLightingModel() { + _lightingModel = std::make_shared(); +} + +void MakeLightingModel::configure(const Config& config) { + _lightingModel->setUnlit(config.enableUnlit); + _lightingModel->setEmissive(config.enableEmissive); + _lightingModel->setLightmap(config.enableLightmap); + _lightingModel->setBackground(config.enableBackground); + + _lightingModel->setObscurance(config.enableObscurance); + + _lightingModel->setScattering(config.enableScattering); + _lightingModel->setDiffuse(config.enableDiffuse); + _lightingModel->setSpecular(config.enableSpecular); + _lightingModel->setAlbedo(config.enableAlbedo); + + _lightingModel->setAmbientLight(config.enableAmbientLight); + _lightingModel->setDirectionalLight(config.enableDirectionalLight); + _lightingModel->setPointLight(config.enablePointLight); + _lightingModel->setSpotLight(config.enableSpotLight); + + _lightingModel->setShowLightContour(config.showLightContour); +} + +void MakeLightingModel::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, LightingModelPointer& lightingModel) { + + lightingModel = _lightingModel; +} \ No newline at end of file diff --git a/libraries/render-utils/src/LightingModel.h b/libraries/render-utils/src/LightingModel.h new file mode 100644 index 0000000000..87a6703dc6 --- /dev/null +++ b/libraries/render-utils/src/LightingModel.h @@ -0,0 +1,166 @@ +// +// LightingModel.h +// libraries/render-utils/src/ +// +// Created by Sam Gateau 7/1/2016. +// Copyright 2016 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_LightingModel_h +#define hifi_LightingModel_h + +#include "gpu/Resource.h" +#include "render/DrawTask.h" + +class RenderArgs; + +// LightingModel is a helper class gathering in one place the flags to enable the lighting contributions +class LightingModel { +public: + using UniformBufferView = gpu::BufferView; + + LightingModel(); + + + void setUnlit(bool enable); + bool isUnlitEnabled() const; + + void setEmissive(bool enable); + bool isEmissiveEnabled() const; + void setLightmap(bool enable); + bool isLightmapEnabled() const; + + void setBackground(bool enable); + bool isBackgroundEnabled() const; + + void setObscurance(bool enable); + bool isObscuranceEnabled() const; + + void setScattering(bool enable); + bool isScatteringEnabled() const; + void setDiffuse(bool enable); + bool isDiffuseEnabled() const; + void setSpecular(bool enable); + bool isSpecularEnabled() const; + + void setAlbedo(bool enable); + bool isAlbedoEnabled() const; + + + void setAmbientLight(bool enable); + bool isAmbientLightEnabled() const; + void setDirectionalLight(bool enable); + bool isDirectionalLightEnabled() const; + void setPointLight(bool enable); + bool isPointLightEnabled() const; + void setSpotLight(bool enable); + bool isSpotLightEnabled() const; + + void setShowLightContour(bool enable); + bool isShowLightContourEnabled() const; + + UniformBufferView getParametersBuffer() const { return _parametersBuffer; } + +protected: + + + // Class describing the uniform buffer with the transform info common to the AO shaders + // It s changing every frame + class Parameters { + public: + float enableUnlit{ 1.0f }; + float enableEmissive{ 1.0f }; + float enableLightmap{ 1.0f }; + float enableBackground{ 1.0f }; + + float enableScattering{ 1.0f }; + float enableDiffuse{ 1.0f }; + float enableSpecular{ 1.0f }; + float enableAlbedo{ 1.0f }; + + + float enableAmbientLight{ 1.0f }; + float enableDirectionalLight{ 1.0f }; + float enablePointLight{ 1.0f }; + float enableSpotLight{ 1.0f }; + + float showLightContour{ 0.0f }; // false by default + float enableObscurance{ 1.0f }; + + glm::vec2 spares{ 0.0f }; + + Parameters() {} + }; + UniformBufferView _parametersBuffer; +}; + +using LightingModelPointer = std::shared_ptr; + + + + +class MakeLightingModelConfig : public render::Job::Config { + Q_OBJECT + + Q_PROPERTY(bool enableUnlit MEMBER enableUnlit NOTIFY dirty) + Q_PROPERTY(bool enableEmissive MEMBER enableEmissive NOTIFY dirty) + Q_PROPERTY(bool enableLightmap MEMBER enableLightmap NOTIFY dirty) + Q_PROPERTY(bool enableBackground MEMBER enableBackground NOTIFY dirty) + + Q_PROPERTY(bool enableObscurance MEMBER enableObscurance NOTIFY dirty) + + Q_PROPERTY(bool enableScattering MEMBER enableScattering NOTIFY dirty) + Q_PROPERTY(bool enableDiffuse MEMBER enableDiffuse NOTIFY dirty) + Q_PROPERTY(bool enableSpecular MEMBER enableSpecular NOTIFY dirty) + Q_PROPERTY(bool enableAlbedo MEMBER enableAlbedo NOTIFY dirty) + + Q_PROPERTY(bool enableAmbientLight MEMBER enableAmbientLight NOTIFY dirty) + Q_PROPERTY(bool enableDirectionalLight MEMBER enableDirectionalLight NOTIFY dirty) + Q_PROPERTY(bool enablePointLight MEMBER enablePointLight NOTIFY dirty) + Q_PROPERTY(bool enableSpotLight MEMBER enableSpotLight NOTIFY dirty) + + Q_PROPERTY(bool showLightContour MEMBER showLightContour NOTIFY dirty) + +public: + MakeLightingModelConfig() : render::Job::Config() {} // Make Lighting Model is always on + + bool enableUnlit{ true }; + bool enableEmissive{ true }; + bool enableLightmap{ true }; + bool enableBackground{ true }; + bool enableObscurance{ true }; + + bool enableScattering{ true }; + bool enableDiffuse{ true }; + bool enableSpecular{ true }; + bool enableAlbedo{ true }; + + bool enableAmbientLight{ true }; + bool enableDirectionalLight{ true }; + bool enablePointLight{ true }; + bool enableSpotLight{ true }; + + bool showLightContour{ false }; // false by default + +signals: + void dirty(); +}; + +class MakeLightingModel { +public: + using Config = MakeLightingModelConfig; + using JobModel = render::Job::ModelO; + + MakeLightingModel(); + + void configure(const Config& config); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, LightingModelPointer& lightingModel); + +private: + LightingModelPointer _lightingModel; +}; + +#endif // hifi_SurfaceGeometryPass_h diff --git a/libraries/render-utils/src/LightingModel.slh b/libraries/render-utils/src/LightingModel.slh new file mode 100644 index 0000000000..f36b2d8131 --- /dev/null +++ b/libraries/render-utils/src/LightingModel.slh @@ -0,0 +1,200 @@ + +<@if not LIGHTING_MODEL_SLH@> +<@def LIGHTING_MODEL_SLH@> + +<@func declareLightingModel()@> + +struct LightingModel { + vec4 _UnlitEmissiveLightmapBackground; + vec4 _ScatteringDiffuseSpecularAlbedo; + vec4 _AmbientDirectionalPointSpot; + vec4 _ShowContourObscuranceSpare2; +}; + +uniform lightingModelBuffer{ + LightingModel lightingModel; +}; + +float isUnlitEnabled() { + return lightingModel._UnlitEmissiveLightmapBackground.x; +} +float isEmissiveEnabled() { + return lightingModel._UnlitEmissiveLightmapBackground.y; +} +float isLightmapEnabled() { + return lightingModel._UnlitEmissiveLightmapBackground.z; +} +float isBackgroundEnabled() { + return lightingModel._UnlitEmissiveLightmapBackground.w; +} +float isObscuranceEnabled() { + return lightingModel._ShowContourObscuranceSpare2.y; +} + +float isScatteringEnabled() { + return lightingModel._ScatteringDiffuseSpecularAlbedo.x; +} +float isDiffuseEnabled() { + return lightingModel._ScatteringDiffuseSpecularAlbedo.y; +} +float isSpecularEnabled() { + return lightingModel._ScatteringDiffuseSpecularAlbedo.z; +} +float isAlbedoEnabled() { + return lightingModel._ScatteringDiffuseSpecularAlbedo.w; +} + +float isAmbientEnabled() { + return lightingModel._AmbientDirectionalPointSpot.x; +} +float isDirectionalEnabled() { + return lightingModel._AmbientDirectionalPointSpot.y; +} +float isPointEnabled() { + return lightingModel._AmbientDirectionalPointSpot.z; +} +float isSpotEnabled() { + return lightingModel._AmbientDirectionalPointSpot.w; +} + +float isShowLightContour() { + return lightingModel._ShowContourObscuranceSpare2.x; +} + + +<@endfunc@> +<$declareLightingModel()$> + +<@func declareBeckmannSpecular()@> + +uniform sampler2D scatteringSpecularBeckmann; + +float fetchSpecularBeckmann(float ndoth, float roughness) { + return pow(2.0 * texture(scatteringSpecularBeckmann, vec2(ndoth, roughness)).r, 10.0); +} + +float fresnelSchlickScalar(float fresnelColor, vec3 lightDir, vec3 halfDir) { + float base = 1.0 - clamp(dot(lightDir, halfDir), 0.0, 1.0); + float exponential = pow(base, 5.0); + return (exponential)+fresnelColor * (1.0 - exponential); +} + +vec2 skinSpecular(vec3 N, vec3 L, vec3 V, float roughness, float intensity) { + vec2 result = vec2(0.0, 1.0); + float ndotl = dot(N, L); + if (ndotl > 0.0) { + vec3 h = L + V; + vec3 H = normalize(h); + float ndoth = dot(N, H); + float PH = fetchSpecularBeckmann(ndoth, roughness); + float F = fresnelSchlickScalar(0.028, H, V); + float frSpec = max(PH * F / dot(h, h), 0.0); + result.x = ndotl * intensity * frSpec; + result.y -= F; + } + + return result; +} +<@endfunc@> + +<@func declareEvalPBRShading()@> + +vec3 fresnelSchlickColor(vec3 fresnelColor, vec3 lightDir, vec3 halfDir) { + float base = 1.0 - clamp(dot(lightDir, halfDir), 0.0, 1.0); + float exponential = pow(base, 5.0); + return vec3(exponential) + fresnelColor * (1.0 - exponential); +} + + + +float specularDistribution(float roughness, vec3 normal, vec3 halfDir) { + float ndoth = clamp(dot(halfDir, normal), 0.0, 1.0); + float gloss2 = pow(0.001 + roughness, 4); + float denom = (ndoth * ndoth*(gloss2 - 1) + 1); + float power = gloss2 / (3.14159 * denom * denom); + return power; +} + +// Frag Shading returns the diffuse amount as W and the specular rgb as xyz +vec4 evalPBRShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, float metallic, vec3 fresnel, float roughness) { + // Diffuse Lighting + float diffuse = clamp(dot(fragNormal, fragLightDir), 0.0, 1.0); + + // Specular Lighting + vec3 halfDir = normalize(fragEyeDir + fragLightDir); + vec3 fresnelColor = fresnelSchlickColor(fresnel, fragLightDir, halfDir); + float power = specularDistribution(roughness, fragNormal, halfDir); + vec3 specular = power * fresnelColor * diffuse; + + return vec4(specular, (1.0 - metallic) * diffuse * (1 - fresnelColor.x)); +} +<@endfunc@> + + + +<$declareEvalPBRShading()$> + +// Return xyz the specular/reflection component and w the diffuse component +//vec4 evalFragShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, float metallic, vec3 fresnel, float roughness) { +// return evalPBRShading(fragNormal, fragLightDir, fragEyeDir, metallic, fresnel, roughness); +//} + +void evalFragShading(out vec3 diffuse, out vec3 specular, + vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, + float metallic, vec3 fresnel, float roughness, vec3 albedo) { + vec4 shading = evalPBRShading(fragNormal, fragLightDir, fragEyeDir, metallic, fresnel, roughness); + diffuse = vec3(shading.w); + if (isAlbedoEnabled() > 0.0) { + diffuse *= albedo; + } + specular = shading.xyz; +} + +<$declareBeckmannSpecular()$> +<@include SubsurfaceScattering.slh@> +<$declareSubsurfaceScatteringBRDF()$> + + +void evalFragShading(out vec3 diffuse, out vec3 specular, + vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, + float metallic, vec3 fresnel, float roughness, vec3 albedo, + float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature) { + if (scattering * isScatteringEnabled() > 0.0) { + vec3 brdf = evalSkinBRDF(fragLightDir, fragNormal, midNormalCurvature.xyz, lowNormalCurvature.xyz, lowNormalCurvature.w); + float NdotL = clamp(dot(fragNormal, fragLightDir), 0.0, 1.0); + diffuse = mix(vec3(NdotL), brdf, scattering); + + // Specular Lighting + vec3 halfDir = normalize(fragEyeDir + fragLightDir); + vec2 specularBrdf = skinSpecular(fragNormal, fragLightDir, fragEyeDir, roughness, 1.0); + + diffuse *= specularBrdf.y; + specular = vec3(specularBrdf.x); + } else { + vec4 shading = evalPBRShading(fragNormal, fragLightDir, fragEyeDir, metallic, fresnel, roughness); + diffuse = vec3(shading.w); + specular = shading.xyz; + } + if (isAlbedoEnabled() > 0.0) { + diffuse *= albedo; + } +} + + +<@endif@> diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/render-utils/src/MaterialTextures.slh index f9b1c76104..7313d87d62 100644 --- a/libraries/render-utils/src/MaterialTextures.slh +++ b/libraries/render-utils/src/MaterialTextures.slh @@ -44,7 +44,7 @@ TexMapArray getTexMapArray() { <@endfunc@> -<@func declareMaterialTextures(withAlbedo, withRoughness, withNormal, withMetallic, withEmissive, withOcclusion)@> +<@func declareMaterialTextures(withAlbedo, withRoughness, withNormal, withMetallic, withEmissive, withOcclusion, withScattering)@> <@if withAlbedo@> uniform sampler2D albedoMap; @@ -87,10 +87,20 @@ float fetchOcclusionMap(vec2 uv) { return texture(occlusionMap, uv).r; } <@endif@> + +<@if withScattering@> +uniform sampler2D scatteringMap; +float fetchScatteringMap(vec2 uv) { + float scattering = texture(scatteringMap, uv).r; // boolean scattering for now + return max(((scattering - 0.1) / 0.9), 0.0); + return texture(scatteringMap, uv).r; // boolean scattering for now +} +<@endif@> + <@endfunc@> -<@func fetchMaterialTextures(matKey, texcoord0, albedo, roughness, normal, metallic, emissive, occlusion)@> +<@func fetchMaterialTexturesCoord0(matKey, texcoord0, albedo, roughness, normal, metallic, emissive, scattering)@> <@if albedo@> vec4 <$albedo$> = (((<$matKey$> & (ALBEDO_MAP_BIT | OPACITY_MASK_MAP_BIT | OPACITY_TRANSLUCENT_MAP_BIT)) != 0) ? fetchAlbedoMap(<$texcoord0$>) : vec4(1.0)); <@endif@> @@ -106,11 +116,21 @@ float fetchOcclusionMap(vec2 uv) { <@if emissive@> vec3 <$emissive$> = (((<$matKey$> & EMISSIVE_MAP_BIT) != 0) ? fetchEmissiveMap(<$texcoord0$>) : vec3(0.0)); <@endif@> -<@if occlusion@> - float <$occlusion$> = (((<$matKey$> & OCCLUSION_MAP_BIT) != 0) ? fetchOcclusionMap(<$texcoord0$>) : 1.0); +<@if scattering@> + float <$scattering$> = (((<$matKey$> & SCATTERING_MAP_BIT) != 0) ? fetchScatteringMap(<$texcoord0$>) : 0.0); <@endif@> <@endfunc@> +<@func fetchMaterialTexturesCoord1(matKey, texcoord1, occlusion, lightmapVal)@> +<@if occlusion@> + float <$occlusion$> = (((<$matKey$> & OCCLUSION_MAP_BIT) != 0) ? fetchOcclusionMap(<$texcoord1$>) : 1.0); +<@endif@> +<@if lightmapVal@> + vec3 <$lightmapVal$> = fetchLightmapMap(<$texcoord1$>); +<@endif@> +<@endfunc@> + + <@func declareMaterialLightmap()@> @@ -123,11 +143,6 @@ vec3 fetchLightmapMap(vec2 uv) { } <@endfunc@> -<@func fetchMaterialLightmap(texcoord1, lightmapVal)@> - vec3 <$lightmapVal$> = fetchLightmapMap(<$texcoord1$>); -<@endfunc@> - - <@func tangentToViewSpace(fetchedNormal, interpolatedNormal, interpolatedTangent, normal)@> { vec3 normalizedNormal = normalize(<$interpolatedNormal$>.xyz); @@ -189,4 +204,10 @@ vec3 fetchLightmapMap(vec2 uv) { } <@endfunc@> +<@func evalMaterialScattering(fetchedScattering, materialScattering, matKey, scattering)@> +{ + <$scattering$> = (((<$matKey$> & SCATTERING_MAP_BIT) != 0) ? <$fetchedScattering$> : <$materialScattering$>); +} +<@endfunc@> + <@endif@> \ No newline at end of file diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 08c8dc23b4..cb6c73f414 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -217,6 +217,20 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, nullptr); } + // Scattering map + if (materialKey.isScatteringMap()) { + auto scatteringMap = textureMaps[model::MaterialKey::SCATTERING_MAP]; + if (scatteringMap && scatteringMap->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, scatteringMap->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); + } + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, nullptr); + } + // Emissive / Lightmap if (materialKey.isLightmapMap()) { auto lightmapMap = textureMaps[model::MaterialKey::LIGHTMAP_MAP]; @@ -310,7 +324,7 @@ ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int par _shapeID(shapeIndex) { assert(_model && _model->isLoaded()); - auto& modelMesh = _model->getGeometry()->getGeometry()->getMeshes().at(_meshIndex); + auto& modelMesh = _model->getGeometry()->getMeshes().at(_meshIndex); updateMeshPart(modelMesh, partIndex); updateTransform(transform, offsetTransform); @@ -331,7 +345,7 @@ void ModelMeshPartPayload::initCache() { _isBlendShaped = !mesh.blendshapes.isEmpty(); } - auto networkMaterial = _model->getGeometry()->getGeometry()->getShapeMaterial(_shapeID); + auto networkMaterial = _model->getGeometry()->getShapeMaterial(_shapeID); if (networkMaterial) { _drawMaterial = networkMaterial; }; @@ -384,7 +398,7 @@ ItemKey ModelMeshPartPayload::getKey() const { ShapeKey ModelMeshPartPayload::getShapeKey() const { assert(_model->isLoaded()); const FBXGeometry& geometry = _model->getFBXGeometry(); - const auto& networkMeshes = _model->getGeometry()->getGeometry()->getMeshes(); + const auto& networkMeshes = _model->getGeometry()->getMeshes(); // guard against partially loaded meshes if (_meshIndex >= (int)networkMeshes.size() || _meshIndex >= (int)geometry.meshes.size() || _meshIndex >= (int)_model->_meshStates.size()) { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 2fe4427333..e2363d0cca 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "AbstractViewStateInterface.h" #include "MeshPartPayload.h" @@ -29,16 +30,59 @@ using namespace std; -static int nakedModelPointerTypeId = qRegisterMetaType(); -static int weakNetworkGeometryPointerTypeId = qRegisterMetaType >(); -static int vec3VectorTypeId = qRegisterMetaType >(); +int nakedModelPointerTypeId = qRegisterMetaType(); +int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType(); +int vec3VectorTypeId = qRegisterMetaType >(); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; #define HTTP_INVALID_COM "http://invalid.com" -model::MaterialPointer Model::_collisionHullMaterial; +const int NUM_COLLISION_HULL_COLORS = 24; +std::vector _collisionHullMaterials; + +void initCollisionHullMaterials() { + // generates bright colors in red, green, blue, yellow, magenta, and cyan spectrums + // (no browns, greys, or dark shades) + float component[NUM_COLLISION_HULL_COLORS] = { + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.2f, 0.4f, 0.6f, 0.8f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 0.8f, 0.6f, 0.4f, 0.2f + }; + _collisionHullMaterials.reserve(NUM_COLLISION_HULL_COLORS); + + // each component gets the same cuve + // but offset by a multiple of one third the full width + int numComponents = 3; + int sectionWidth = NUM_COLLISION_HULL_COLORS / numComponents; + int greenPhase = sectionWidth; + int bluePhase = 2 * sectionWidth; + + // we stride through the colors to scatter adjacent shades + // so they don't tend to group together for large models + for (int i = 0; i < sectionWidth; ++i) { + for (int j = 0; j < numComponents; ++j) { + model::MaterialPointer material; + material = std::make_shared(); + int index = j * sectionWidth + i; + float red = component[index]; + float green = component[(index + greenPhase) % NUM_COLLISION_HULL_COLORS]; + float blue = component[(index + bluePhase) % NUM_COLLISION_HULL_COLORS]; + material->setAlbedo(glm::vec3(red, green, blue)); + material->setMetallic(0.02f); + material->setRoughness(0.5f); + _collisionHullMaterials.push_back(material); + } + } +} Model::Model(RigPointer rig, QObject* parent) : QObject(parent), + _renderGeometry(), + _collisionGeometry(), + _renderWatcher(_renderGeometry), + _collisionWatcher(_collisionGeometry), _translation(0.0f), _rotation(), _scale(1.0f, 1.0f, 1.0f), @@ -58,7 +102,6 @@ Model::Model(RigPointer rig, QObject* parent) : _calculatedMeshTrianglesValid(false), _meshGroupsKnown(false), _isWireframe(false), - _renderCollisionHull(false), _rig(rig) { // we may have been created in the network thread, but we live in the main thread if (_viewState) { @@ -76,7 +119,7 @@ AbstractViewStateInterface* Model::_viewState = NULL; bool Model::needsFixupInScene() const { if (readyToAddToScene()) { - if (_needsUpdateTextures && _geometry->getGeometry()->areTexturesLoaded()) { + if (_needsUpdateTextures && _renderGeometry->areTexturesLoaded()) { _needsUpdateTextures = false; return true; } @@ -726,10 +769,6 @@ glm::vec3 Model::calculateScaledOffsetPoint(const glm::vec3& point) const { return translatedPoint; } -bool Model::getJointState(int index, glm::quat& rotation) const { - return _rig->getJointStateRotation(index, rotation); -} - void Model::clearJointState(int index) { _rig->clearJointState(index); } @@ -757,13 +796,13 @@ int Model::getLastFreeJointIndex(int jointIndex) const { void Model::setTextures(const QVariantMap& textures) { if (isLoaded()) { _needsUpdateTextures = true; - _geometry->getGeometry()->setTextures(textures); + _renderGeometry->setTextures(textures); } } void Model::setURL(const QUrl& url) { // don't recreate the geometry if it's the same URL - if (_url == url && _geometry && _geometry->getURL() == url) { + if (_url == url && _renderWatcher.getURL() == url) { return; } @@ -782,16 +821,16 @@ void Model::setURL(const QUrl& url) { invalidCalculatedMeshBoxes(); deleteGeometry(); - _geometry = DependencyManager::get()->getGeometry(url); + _renderWatcher.setResource(DependencyManager::get()->getGeometryResource(url)); onInvalidate(); } void Model::setCollisionModelURL(const QUrl& url) { - if (_collisionUrl == url) { + if (_collisionUrl == url && _collisionWatcher.getURL() == url) { return; } _collisionUrl = url; - _collisionGeometry = DependencyManager::get()->getGeometry(url); + _collisionWatcher.setResource(DependencyManager::get()->getGeometryResource(url)); } bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const { @@ -847,7 +886,7 @@ QStringList Model::getJointNames() const { class Blender : public QRunnable { public: - Blender(ModelPointer model, int blendNumber, const std::weak_ptr& geometry, + Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& meshes, const QVector& blendshapeCoefficients); virtual void run(); @@ -856,12 +895,12 @@ private: ModelPointer _model; int _blendNumber; - std::weak_ptr _geometry; + Geometry::WeakPointer _geometry; QVector _meshes; QVector _blendshapeCoefficients; }; -Blender::Blender(ModelPointer model, int blendNumber, const std::weak_ptr& geometry, +Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& meshes, const QVector& blendshapeCoefficients) : _model(model), _blendNumber(blendNumber), @@ -904,7 +943,7 @@ void Blender::run() { // post the result to the geometry cache, which will dispatch to the model if still alive QMetaObject::invokeMethod(DependencyManager::get().data(), "setBlendedVertices", Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), - Q_ARG(const std::weak_ptr&, _geometry), Q_ARG(const QVector&, vertices), + Q_ARG(const Geometry::WeakPointer&, _geometry), Q_ARG(const QVector&, vertices), Q_ARG(const QVector&, normals)); } @@ -967,7 +1006,7 @@ void Model::scaleToFit() { Extents modelMeshExtents = getUnscaledMeshExtents(); // size is our "target size in world space" - // we need to set our model scale so that the extents of the mesh, fit in a cube that size... + // we need to set our model scale so that the extents of the mesh, fit in a box that size... glm::vec3 meshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum; glm::vec3 rescaleDimensions = _scaleToFitDimensions / meshDimensions; setScaleInternal(rescaleDimensions); @@ -1115,7 +1154,7 @@ bool Model::maybeStartBlender() { if (isLoaded()) { const FBXGeometry& fbxGeometry = getFBXGeometry(); if (fbxGeometry.hasBlendedMeshes()) { - QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _geometry, + QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _renderGeometry, fbxGeometry.meshes, _blendshapeCoefficients)); return true; } @@ -1123,10 +1162,10 @@ bool Model::maybeStartBlender() { return false; } -void Model::setBlendedVertices(int blendNumber, const std::weak_ptr& geometry, +void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals) { auto geometryRef = geometry.lock(); - if (!geometryRef || _geometry != geometryRef || _blendedVertexBuffers.empty() || blendNumber < _appliedBlendNumber) { + if (!geometryRef || _renderGeometry != geometryRef || _blendedVertexBuffers.empty() || blendNumber < _appliedBlendNumber) { return; } _appliedBlendNumber = blendNumber; @@ -1169,27 +1208,23 @@ AABox Model::getRenderableMeshBound() const { } void Model::segregateMeshGroups() { - NetworkGeometry::Pointer networkGeometry; + Geometry::Pointer geometry; bool showingCollisionHull = false; if (_showCollisionHull && _collisionGeometry) { if (isCollisionLoaded()) { - networkGeometry = _collisionGeometry; + geometry = _collisionGeometry; showingCollisionHull = true; } else { return; } } else { assert(isLoaded()); - networkGeometry = _geometry; + geometry = _renderGeometry; } - const FBXGeometry& geometry = networkGeometry->getGeometry()->getGeometry(); - const auto& networkMeshes = networkGeometry->getGeometry()->getMeshes(); + const auto& meshes = geometry->getMeshes(); // all of our mesh vectors must match in size - auto geoMeshesSize = geometry.meshes.size(); - if ((int)networkMeshes.size() != geoMeshesSize || - // geometry.meshes.size() != _meshStates.size()) { - geoMeshesSize > _meshStates.size()) { + if ((int)meshes.size() != _meshStates.size()) { qDebug() << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet."; return; } @@ -1213,26 +1248,25 @@ void Model::segregateMeshGroups() { // Run through all of the meshes, and place them into their segregated, but unsorted buckets int shapeID = 0; - for (int i = 0; i < (int)networkMeshes.size(); i++) { - const FBXMesh& mesh = geometry.meshes.at(i); - const auto& networkMesh = networkMeshes.at(i); + uint32_t numMeshes = (uint32_t)meshes.size(); + for (uint32_t i = 0; i < numMeshes; i++) { + const auto& mesh = meshes.at(i); + if (mesh) { - // Create the render payloads - int totalParts = mesh.parts.size(); - for (int partIndex = 0; partIndex < totalParts; partIndex++) { - if (showingCollisionHull) { - if (!_collisionHullMaterial) { - _collisionHullMaterial = std::make_shared(); - _collisionHullMaterial->setAlbedo(glm::vec3(1.0f, 0.5f, 0.0f)); - _collisionHullMaterial->setMetallic(0.02f); - _collisionHullMaterial->setRoughness(0.5f); + // Create the render payloads + int numParts = (int)mesh->getNumParts(); + for (int partIndex = 0; partIndex < numParts; partIndex++) { + if (showingCollisionHull) { + if (_collisionHullMaterials.empty()) { + initCollisionHullMaterials(); + } + _collisionRenderItemsSet << std::make_shared(mesh, partIndex, _collisionHullMaterials[partIndex % NUM_COLLISION_HULL_COLORS], transform, offset); + } else { + _modelMeshRenderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset); } - _collisionRenderItemsSet << std::make_shared(networkMesh, partIndex, _collisionHullMaterial, transform, offset); - } else { - _modelMeshRenderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset); - } - shapeID++; + shapeID++; + } } } _meshGroupsKnown = true; @@ -1295,7 +1329,7 @@ void ModelBlender::noteRequiresBlend(ModelPointer model) { } void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, - const std::weak_ptr& geometry, const QVector& vertices, const QVector& normals) { + const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals) { if (model) { model->setBlendedVertices(blendNumber, geometry, vertices, normals); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 581184918d..aa0c49f720 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -97,7 +97,7 @@ public: bool showCollisionHull = false); void removeFromScene(std::shared_ptr scene, render::PendingChanges& pendingChanges); void renderSetup(RenderArgs* args); - bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && getGeometry()->getGeometry()->getMeshes().empty()); } + bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _renderGeometry->getMeshes().empty()); } bool isVisible() const { return _isVisible; } @@ -107,11 +107,11 @@ public: bool maybeStartBlender(); /// Sets blended vertices computed in a separate thread. - void setBlendedVertices(int blendNumber, const std::weak_ptr& geometry, + void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals); - bool isLoaded() const { return _geometry && _geometry->getGeometry(); } - bool isCollisionLoaded() const { return _collisionGeometry && _collisionGeometry->getGeometry(); } + bool isLoaded() const { return (bool)_renderGeometry; } + bool isCollisionLoaded() const { return (bool)_collisionGeometry; } void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } bool isWireframe() const { return _isWireframe; } @@ -128,18 +128,18 @@ public: virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation); /// Returns a reference to the shared geometry. - const NetworkGeometry::Pointer& getGeometry() const { return _geometry; } + const Geometry::Pointer& getGeometry() const { return _renderGeometry; } /// Returns a reference to the shared collision geometry. - const NetworkGeometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } + const Geometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } - const QVariantMap getTextures() const { assert(isLoaded()); return _geometry->getGeometry()->getTextures(); } + const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); } void setTextures(const QVariantMap& textures); /// Provided as a convenience, will crash if !isLoaded() // And so that getGeometry() isn't chained everywhere - const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return getGeometry()->getGeometry()->getGeometry(); } + const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return _renderGeometry->getFBXGeometry(); } /// Provided as a convenience, will crash if !isCollisionLoaded() - const FBXGeometry& getCollisionFBXGeometry() const { assert(isCollisionLoaded()); return getCollisionGeometry()->getGeometry()->getGeometry(); } + const FBXGeometry& getCollisionFBXGeometry() const { assert(isCollisionLoaded()); return _collisionGeometry->getFBXGeometry(); } // Set the model to use for collisions. // Should only be called from the model's rendering thread to avoid access violations of changed geometry. @@ -252,10 +252,6 @@ protected: /// Returns the scaled equivalent of a point in model space. glm::vec3 calculateScaledOffsetPoint(const glm::vec3& point) const; - /// Fetches the joint state at the specified index. - /// \return whether or not the joint state is "valid" (that is, non-default) - bool getJointState(int index, glm::quat& rotation) const; - /// Clear the joint states void clearJointState(int index); @@ -267,7 +263,11 @@ protected: /// \return true if joint exists bool getJointPosition(int jointIndex, glm::vec3& position) const; - NetworkGeometry::Pointer _geometry; + Geometry::Pointer _renderGeometry; // only ever set by its watcher + Geometry::Pointer _collisionGeometry; // only ever set by its watcher + + GeometryResourceWatcher _renderWatcher; + GeometryResourceWatcher _collisionWatcher; glm::vec3 _translation; glm::quat _rotation; @@ -334,8 +334,6 @@ protected: void deleteGeometry(); void initJointTransforms(); - NetworkGeometry::Pointer _collisionGeometry; - float _pupilDilation; QVector _blendshapeCoefficients; @@ -377,9 +375,6 @@ protected: static AbstractViewStateInterface* _viewState; - bool _renderCollisionHull; - - QSet> _collisionRenderItemsSet; QMap _collisionRenderItems; @@ -399,7 +394,7 @@ protected: }; Q_DECLARE_METATYPE(ModelPointer) -Q_DECLARE_METATYPE(std::weak_ptr) +Q_DECLARE_METATYPE(Geometry::WeakPointer) /// Handle management of pending models that need blending class ModelBlender : public QObject, public Dependency { @@ -412,7 +407,7 @@ public: void noteRequiresBlend(ModelPointer model); public slots: - void setBlendedVertices(ModelPointer model, int blendNumber, const std::weak_ptr& geometry, + void setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals); private: diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 444c52623e..bb7adf3f80 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -23,9 +23,13 @@ #include #include #include +#include +#include "LightingModel.h" #include "DebugDeferredBuffer.h" +#include "DeferredFramebuffer.h" #include "DeferredLightingEffect.h" +#include "SurfaceGeometryPass.h" #include "FramebufferCache.h" #include "HitEffect.h" #include "TextureCache.h" @@ -33,21 +37,17 @@ #include "AmbientOcclusionEffect.h" #include "AntialiasingEffect.h" #include "ToneMappingEffect.h" +#include "SubsurfaceScattering.h" + +#include + +#include "drawOpaqueStencil_frag.h" + using namespace render; - -extern void initStencilPipeline(gpu::PipelinePointer& pipeline); extern void initOverlay3DPipelines(render::ShapePlumber& plumber); extern void initDeferredPipelines(render::ShapePlumber& plumber); -void PrepareDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { - DependencyManager::get()->prepare(renderContext->args); -} - -void RenderDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { - DependencyManager::get()->render(renderContext); -} - RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) { cullFunctor = cullFunctor ? cullFunctor : [](const RenderArgs*, const AABox&){ return true; }; @@ -92,17 +92,53 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) { const auto overlayTransparents = addJob("DepthSortOverlayTransparent", filteredNonspatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false)); const auto background = filteredNonspatialBuckets[BACKGROUND_BUCKET]; - // GPU jobs: Start preparing the deferred and lighting buffer - addJob("PrepareDeferred"); + // Prepare deferred, generate the shared Deferred Frame Transform + const auto deferredFrameTransform = addJob("DeferredFrameTransform"); + const auto lightingModel = addJob("LightingModel"); + + + // GPU jobs: Start preparing the primary, deferred and lighting buffer + const auto primaryFramebuffer = addJob("PreparePrimaryBuffer"); + + // const auto fullFrameRangeTimer = addJob("BeginRangeTimer"); + const auto opaqueRangeTimer = addJob("BeginOpaqueRangeTimer"); + + const auto prepareDeferredInputs = PrepareDeferred::Inputs(primaryFramebuffer, lightingModel).hasVarying(); + const auto prepareDeferredOutputs = addJob("PrepareDeferred", prepareDeferredInputs); + const auto deferredFramebuffer = prepareDeferredOutputs.getN(0); + const auto lightingFramebuffer = prepareDeferredOutputs.getN(1); // Render opaque objects in DeferredBuffer - addJob("DrawOpaqueDeferred", opaques, shapePlumber); + const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).hasVarying(); + addJob("DrawOpaqueDeferred", opaqueInputs, shapePlumber); // Once opaque is all rendered create stencil background - addJob("DrawOpaqueStencil"); + addJob("DrawOpaqueStencil", deferredFramebuffer); - // Use Stencil and start drawing background in Lighting buffer - addJob("DrawBackgroundDeferred", background); + addJob("OpaqueRangeTimer", opaqueRangeTimer); + + + // Opaque all rendered + + // Linear Depth Pass + const auto linearDepthPassInputs = LinearDepthPass::Inputs(deferredFrameTransform, deferredFramebuffer).hasVarying(); + const auto linearDepthPassOutputs = addJob("LinearDepth", linearDepthPassInputs); + const auto linearDepthTarget = linearDepthPassOutputs.getN(0); + const auto linearDepthTexture = linearDepthPassOutputs.getN(2); + const auto halfLinearDepthTexture = linearDepthPassOutputs.getN(3); + const auto halfNormalTexture = linearDepthPassOutputs.getN(4); + + + // Curvature pass + const auto surfaceGeometryPassInputs = SurfaceGeometryPass::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget).hasVarying(); + const auto surfaceGeometryPassOutputs = addJob("SurfaceGeometry", surfaceGeometryPassInputs); + const auto surfaceGeometryFramebuffer = surfaceGeometryPassOutputs.getN(0); + const auto curvatureFramebuffer = surfaceGeometryPassOutputs.getN(1); + const auto midCurvatureNormalFramebuffer = surfaceGeometryPassOutputs.getN(2); + const auto lowCurvatureNormalFramebuffer = surfaceGeometryPassOutputs.getN(3); + + // Simply update the scattering resource + const auto scatteringResource = addJob("Scattering"); // AO job addJob("AmbientOcclusion"); @@ -110,27 +146,40 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) { // Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now. addJob("DrawLight", lights); - // DeferredBuffer is complete, now let's shade it into the LightingBuffer - addJob("RenderDeferred"); + const auto deferredLightingInputs = RenderDeferred::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, + surfaceGeometryFramebuffer, lowCurvatureNormalFramebuffer, scatteringResource).hasVarying(); - // AA job to be revisited - addJob("Antialiasing"); + // DeferredBuffer is complete, now let's shade it into the LightingBuffer + addJob("RenderDeferred", deferredLightingInputs); + + // Use Stencil and draw background in Lighting buffer to complete filling in the opaque + const auto backgroundInputs = DrawBackgroundDeferred::Inputs(background, lightingModel).hasVarying(); + addJob("DrawBackgroundDeferred", backgroundInputs); // Render transparent objects forward in LightingBuffer - addJob("DrawTransparentDeferred", transparents, shapePlumber); - + const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).hasVarying(); + addJob("DrawTransparentDeferred", transparentsInputs, shapePlumber); + + const auto toneAndPostRangeTimer = addJob("BeginToneAndPostRangeTimer"); + // Lighting Buffer ready for tone mapping - addJob("ToneMapping"); + const auto toneMappingInputs = render::Varying(ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer)); + addJob("ToneMapping", toneMappingInputs); // Overlays - addJob("DrawOverlay3DOpaque", overlayOpaques, true); - addJob("DrawOverlay3DTransparent", overlayTransparents, false); - + const auto overlayOpaquesInputs = DrawOverlay3D::Inputs(overlayOpaques, lightingModel).hasVarying(); + const auto overlayTransparentsInputs = DrawOverlay3D::Inputs(overlayTransparents, lightingModel).hasVarying(); + addJob("DrawOverlay3DOpaque", overlayOpaquesInputs, true); + addJob("DrawOverlay3DTransparent", overlayTransparentsInputs, false); + // Debugging stages { + addJob("DebugScattering", deferredLightingInputs); + // Debugging Deferred buffer job - addJob("DebugDeferredBuffer"); + const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, lowCurvatureNormalFramebuffer)); + addJob("DebugDeferredBuffer", debugFramebuffers); // Scene Octree Debuging job { @@ -147,11 +196,17 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) { } } - // FIXME: Hit effect is never used, let's hide it for now, probably a more generic way to add custom post process effects - // addJob("HitEffect"); + + // AA job to be revisited + addJob("Antialiasing", primaryFramebuffer); + + addJob("ToneAndPostRangeTimer", toneAndPostRangeTimer); // Blit! - addJob("Blit"); + addJob("Blit", primaryFramebuffer); + + // addJob("RangeTimer", fullFrameRangeTimer); + } void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { @@ -167,21 +222,45 @@ void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const Rend return; } + auto config = std::static_pointer_cast(renderContext->jobConfig); + for (auto job : _jobs) { job.run(sceneContext, renderContext); } } -void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) { +void BeginGPURangeTimer::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer) { + timer = _gpuTimer; + gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + _gpuTimer->begin(batch); + }); +} + +void EndGPURangeTimer::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const gpu::RangeTimerPointer& timer) { + gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + timer->end(batch); + }); + + auto config = std::static_pointer_cast(renderContext->jobConfig); + config->gpuTime = timer->getAverage(); +} + + +void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const Inputs& inputs) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); auto config = std::static_pointer_cast(renderContext->jobConfig); + const auto& inItems = inputs.get0(); + const auto& lightingModel = inputs.get1(); + RenderArgs* args = renderContext->args; gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; + + // Setup camera, projection and viewport for all items batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); @@ -193,6 +272,9 @@ void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderCont batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); + // Setup lighting model for all items; + batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn); args->_batch = nullptr; }); @@ -200,16 +282,21 @@ void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderCont config->setNumDrawn((int)inItems.size()); } -void DrawStateSortDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) { +void DrawStateSortDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const Inputs& inputs) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); auto config = std::static_pointer_cast(renderContext->jobConfig); + const auto& inItems = inputs.get0(); + const auto& lightingModel = inputs.get1(); + RenderArgs* args = renderContext->args; gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; + + // Setup camera, projection and viewport for all items batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); @@ -221,6 +308,9 @@ void DrawStateSortDeferred::run(const SceneContextPointer& sceneContext, const R batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); + // Setup lighting model for all items; + batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + if (_stateSort) { renderStateSortShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn); } else { @@ -238,12 +328,15 @@ DrawOverlay3D::DrawOverlay3D(bool opaque) : initOverlay3DPipelines(*_shapePlumber); } -void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const render::ItemBounds& inItems) { +void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const Inputs& inputs) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); auto config = std::static_pointer_cast(renderContext->jobConfig); + const auto& inItems = inputs.get0(); + const auto& lightingModel = inputs.get1(); + config->setNumDrawn((int)inItems.size()); emit config->numDrawnChanged(); @@ -274,21 +367,35 @@ void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderCon batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); + // Setup lighting model for all items; + batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn); args->_batch = nullptr; }); } } -gpu::PipelinePointer DrawStencilDeferred::_opaquePipeline; -const gpu::PipelinePointer& DrawStencilDeferred::getOpaquePipeline() { + +gpu::PipelinePointer DrawStencilDeferred::getOpaquePipeline() { if (!_opaquePipeline) { - initStencilPipeline(_opaquePipeline); + const gpu::int8 STENCIL_OPAQUE = 1; + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(drawOpaqueStencil_frag)); + auto program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::makeProgram((*program)); + + auto state = std::make_shared(); + state->setDepthTest(true, false, gpu::LESS_EQUAL); + 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 = gpu::Pipeline::create(program, state); } return _opaquePipeline; } -void DrawStencilDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { +void DrawStencilDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const DeferredFramebufferPointer& deferredFramebuffer) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); @@ -297,7 +404,8 @@ void DrawStencilDeferred::run(const SceneContextPointer& sceneContext, const Ren doInBatch(args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; - auto deferredFboColorDepthStencil = DependencyManager::get()->getDeferredFramebufferDepthColor(); + auto deferredFboColorDepthStencil = deferredFramebuffer->getDeferredFramebufferDepthColor(); + batch.enableStereo(false); @@ -314,21 +422,23 @@ void DrawStencilDeferred::run(const SceneContextPointer& sceneContext, const Ren args->_batch = nullptr; } -void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) { +void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const Inputs& inputs) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); + const auto& inItems = inputs.get0(); + const auto& lightingModel = inputs.get1(); + if (!lightingModel->isBackgroundEnabled()) { + return; + } + RenderArgs* args = renderContext->args; doInBatch(args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; - _gpuTimer.begin(batch); - - auto lightingFBO = DependencyManager::get()->getLightingFramebuffer(); + // _gpuTimer.begin(batch); batch.enableSkybox(true); - - batch.setFramebuffer(lightingFBO); - + batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); @@ -341,14 +451,14 @@ void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const batch.setViewTransform(viewMat); renderItems(sceneContext, renderContext, inItems); - _gpuTimer.end(batch); + // _gpuTimer.end(batch); }); args->_batch = nullptr; - std::static_pointer_cast(renderContext->jobConfig)->gpuTime = _gpuTimer.getAverage(); + // std::static_pointer_cast(renderContext->jobConfig)->gpuTime = _gpuTimer.getAverage(); } -void Blit::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { +void Blit::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { assert(renderContext->args); assert(renderContext->args->_context); @@ -364,8 +474,7 @@ void Blit::run(const SceneContextPointer& sceneContext, const RenderContextPoint int height = renderArgs->_viewport.w; // Blit primary to blit FBO - auto framebufferCache = DependencyManager::get(); - auto primaryFbo = framebufferCache->getPrimaryFramebuffer(); + auto primaryFbo = srcFramebuffer; gpu::doInBatch(renderArgs->_context, [&](gpu::Batch& batch) { batch.setFramebuffer(blitFbo); diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 5588d1ac28..749cc09edc 100755 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -14,28 +14,46 @@ #include #include +#include "LightingModel.h" -class SetupDeferred { + +class BeginGPURangeTimer { public: - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); - - using JobModel = render::Job::Model; + using JobModel = render::Job::ModelO; + + BeginGPURangeTimer() : _gpuTimer(std::make_shared()) {} + + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer); + +protected: + gpu::RangeTimerPointer _gpuTimer; }; -class PrepareDeferred { +class GPURangeTimerConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(double gpuTime READ getGpuTime) public: - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); - - using JobModel = render::Job::Model; + double getGpuTime() { return gpuTime; } + +protected: + friend class EndGPURangeTimer; + double gpuTime; +}; + +class EndGPURangeTimer { +public: + using Config = GPURangeTimerConfig; + using JobModel = render::Job::ModelI; + + EndGPURangeTimer() {} + + void configure(const Config& config) {} + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const gpu::RangeTimerPointer& timer); + +protected: }; -class RenderDeferred { -public: - using JobModel = render::Job::Model; - - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); -}; class DrawConfig : public render::Job::Config { Q_OBJECT @@ -59,13 +77,14 @@ protected: class DrawDeferred { public: + using Inputs = render::VaryingSet2 ; using Config = DrawConfig; - using JobModel = render::Job::ModelI; + using JobModel = render::Job::ModelI; DrawDeferred(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {} void configure(const Config& config) { _maxDrawn = config.maxDrawn; } - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemBounds& inItems); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs); protected: render::ShapePlumberPointer _shapePlumber; @@ -95,13 +114,15 @@ protected: class DrawStateSortDeferred { public: + using Inputs = render::VaryingSet2 ; + using Config = DrawStateSortConfig; - using JobModel = render::Job::ModelI; + using JobModel = render::Job::ModelI; DrawStateSortDeferred(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {} void configure(const Config& config) { _maxDrawn = config.maxDrawn; _stateSort = config.stateSort; } - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemBounds& inItems); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs); protected: render::ShapePlumberPointer _shapePlumber; @@ -109,15 +130,17 @@ protected: bool _stateSort; }; +class DeferredFramebuffer; class DrawStencilDeferred { public: - using JobModel = render::Job::Model; + using JobModel = render::Job::ModelI>; - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); - static const gpu::PipelinePointer& getOpaquePipeline(); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const std::shared_ptr& deferredFramebuffer); protected: - static gpu::PipelinePointer _opaquePipeline; //lazy evaluation hence mutable + gpu::PipelinePointer _opaquePipeline; + + gpu::PipelinePointer getOpaquePipeline(); }; class DrawBackgroundDeferredConfig : public render::Job::Config { @@ -133,11 +156,13 @@ protected: class DrawBackgroundDeferred { public: + using Inputs = render::VaryingSet2 ; + using Config = DrawBackgroundDeferredConfig; - using JobModel = render::Job::ModelI; + using JobModel = render::Job::ModelI; void configure(const Config& config) {} - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemBounds& inItems); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs); protected: gpu::RangeTimer _gpuTimer; @@ -163,13 +188,15 @@ protected: class DrawOverlay3D { public: + using Inputs = render::VaryingSet2 ; + using Config = DrawOverlay3DConfig; - using JobModel = render::Job::ModelI; + using JobModel = render::Job::ModelI; DrawOverlay3D(bool opaque); void configure(const Config& config) { _maxDrawn = config.maxDrawn; } - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemBounds& inItems); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs); protected: render::ShapePlumberPointer _shapePlumber; @@ -179,18 +206,34 @@ protected: class Blit { public: - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); + using JobModel = render::Job::ModelI; - using JobModel = render::Job::Model; + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer); +}; + +class RenderDeferredTaskConfig : public render::Task::Config { + Q_OBJECT + Q_PROPERTY(double gpuTime READ getGpuTime) +public: + double getGpuTime() { return gpuTime; } + +protected: + friend class RenderDeferredTask; + double gpuTime; }; class RenderDeferredTask : public render::Task { public: + using Config = RenderDeferredTaskConfig; RenderDeferredTask(render::CullFunctor cullFunctor); + void configure(const Config& config) {} void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); - using JobModel = Model; + using JobModel = Model; + +protected: + gpu::RangeTimer _gpuTimer; }; #endif // hifi_RenderDeferredTask_h diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 16681fd363..e50492fd64 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -45,25 +45,9 @@ #include "overlay3D_unlit_frag.h" #include "overlay3D_translucent_unlit_frag.h" -#include "drawOpaqueStencil_frag.h" using namespace render; -void initStencilPipeline(gpu::PipelinePointer& pipeline) { - const gpu::int8 STENCIL_OPAQUE = 1; - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(drawOpaqueStencil_frag)); - auto program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram((*program)); - - auto state = std::make_shared(); - state->setDepthTest(true, false, gpu::LESS_EQUAL); - 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); - - pipeline = gpu::Pipeline::create(program, state); -} - gpu::BufferView getDefaultMaterialBuffer() { model::Material::Schema schema; schema._albedo = vec3(1.0f); diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index f695e2d04c..2e3901a769 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -52,17 +52,19 @@ void RenderShadowMap::run(const render::SceneContextPointer& sceneContext, const batch.setFramebuffer(fbo); batch.clearFramebuffer( gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH, - vec4(vec3(1.0, 1.0, 1.0), 1.0), 1.0, 0, true); + vec4(vec3(1.0, 1.0, 1.0), 0.0), 1.0, 0, true); batch.setProjectionTransform(shadow.getProjection()); batch.setViewTransform(shadow.getView()); auto shadowPipeline = _shapePlumber->pickPipeline(args, ShapeKey()); auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, ShapeKey::Builder().withSkinned()); - args->_pipeline = shadowPipeline; - batch.setPipeline(shadowPipeline->pipeline); std::vector skinnedShapeKeys{}; + + // Iterate through all inShapes and render the unskinned + args->_pipeline = shadowPipeline; + batch.setPipeline(shadowPipeline->pipeline); for (auto items : inShapes) { if (items.first.isSkinned()) { skinnedShapeKeys.push_back(items.first); @@ -71,6 +73,7 @@ void RenderShadowMap::run(const render::SceneContextPointer& sceneContext, const } } + // Reiterate to render the skinned args->_pipeline = shadowSkinnedPipeline; batch.setPipeline(shadowSkinnedPipeline->pipeline); for (const auto& key : skinnedShapeKeys) { @@ -82,8 +85,7 @@ void RenderShadowMap::run(const render::SceneContextPointer& sceneContext, const }); } -// The shadow task *must* use this base ctor to initialize with its own Config, see Task.h -RenderShadowTask::RenderShadowTask(CullFunctor cullFunctor) : Task(std::make_shared()) { +RenderShadowTask::RenderShadowTask(CullFunctor cullFunctor) { cullFunctor = cullFunctor ? cullFunctor : [](const RenderArgs*, const AABox&){ return true; }; // Prepare the ShapePipeline diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 43a20057cd..7b86b9b660 100755 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -87,7 +87,8 @@ float evalShadowAttenuationPCF(vec4 position, vec4 shadowTexcoord) { float evalShadowAttenuation(vec4 position) { vec4 shadowTexcoord = evalShadowTexcoord(position); if (shadowTexcoord.x < 0.0 || shadowTexcoord.x > 1.0 || - shadowTexcoord.y < 0.0 || shadowTexcoord.y > 1.0) { + shadowTexcoord.y < 0.0 || shadowTexcoord.y > 1.0 || + shadowTexcoord.z < 0.0 || shadowTexcoord.z > 1.0) { // If a point is not in the map, do not attenuate return 1.0; } diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp new file mode 100644 index 0000000000..f1aec66433 --- /dev/null +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -0,0 +1,593 @@ +// +// SubsurfaceScattering.cpp +// libraries/render-utils/src/ +// +// Created by Sam Gateau 6/3/2016. +// Copyright 2016 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 "SubsurfaceScattering.h" + +#include +#include + +#include "FramebufferCache.h" + +#include "DeferredLightingEffect.h" + +#include "subsurfaceScattering_makeProfile_frag.h" +#include "subsurfaceScattering_makeLUT_frag.h" +#include "subsurfaceScattering_makeSpecularBeckmann_frag.h" + +#include "subsurfaceScattering_drawScattering_frag.h" + +enum ScatteringShaderBufferSlots { + ScatteringTask_FrameTransformSlot = 0, + ScatteringTask_ParamSlot, + ScatteringTask_LightSlot, +}; +enum ScatteringShaderMapSlots { + ScatteringTask_ScatteringTableSlot = 0, + ScatteringTask_CurvatureMapSlot, + ScatteringTask_DiffusedCurvatureMapSlot, + ScatteringTask_NormalMapSlot, + + ScatteringTask_AlbedoMapSlot, + ScatteringTask_LinearMapSlot, + + ScatteringTask_IBLMapSlot, + +}; + +SubsurfaceScatteringResource::SubsurfaceScatteringResource() { + Parameters parameters; + _parametersBuffer = gpu::BufferView(std::make_shared(sizeof(Parameters), (const gpu::Byte*) ¶meters)); + +} + +void SubsurfaceScatteringResource::setBentNormalFactors(const glm::vec4& rgbsBentFactors) { + if (rgbsBentFactors != getBentNormalFactors()) { + _parametersBuffer.edit().normalBentInfo = rgbsBentFactors; + } +} + +glm::vec4 SubsurfaceScatteringResource::getBentNormalFactors() const { + return _parametersBuffer.get().normalBentInfo; +} + +void SubsurfaceScatteringResource::setCurvatureFactors(const glm::vec2& sbCurvatureFactors) { + if (sbCurvatureFactors != getCurvatureFactors()) { + _parametersBuffer.edit().curvatureInfo = sbCurvatureFactors; + } +} + +glm::vec2 SubsurfaceScatteringResource::getCurvatureFactors() const { + return _parametersBuffer.get().curvatureInfo; +} + + +void SubsurfaceScatteringResource::setLevel(float level) { + if (level != getLevel()) { + _parametersBuffer.edit().level = level; + } +} +float SubsurfaceScatteringResource::getLevel() const { + return _parametersBuffer.get().level; +} + +void SubsurfaceScatteringResource::setShowBRDF(bool show) { + if (show != isShowBRDF()) { + _parametersBuffer.edit().showBRDF = show; + } +} +bool SubsurfaceScatteringResource::isShowBRDF() const { + return (bool)_parametersBuffer.get().showBRDF; +} + +void SubsurfaceScatteringResource::setShowCurvature(bool show) { + if (show != isShowCurvature()) { + _parametersBuffer.edit().showCurvature = show; + } +} +bool SubsurfaceScatteringResource::isShowCurvature() const { + return (bool)_parametersBuffer.get().showCurvature; +} + +void SubsurfaceScatteringResource::setShowDiffusedNormal(bool show) { + if (show != isShowDiffusedNormal()) { + _parametersBuffer.edit().showDiffusedNormal = show; + } +} +bool SubsurfaceScatteringResource::isShowDiffusedNormal() const { + return (bool)_parametersBuffer.get().showDiffusedNormal; +} + +void SubsurfaceScatteringResource::generateScatteringTable(RenderArgs* args) { + if (!_scatteringProfile) { + _scatteringProfile = generateScatteringProfile(args); + } + if (!_scatteringTable) { + _scatteringTable = generatePreIntegratedScattering(_scatteringProfile, args); + } + if (!_scatteringSpecular) { + _scatteringSpecular = generateScatteringSpecularBeckmann(args); + } +} + +SubsurfaceScattering::SubsurfaceScattering() { + _scatteringResource = std::make_shared(); +} + +void SubsurfaceScattering::configure(const Config& config) { + + glm::vec4 bentInfo(config.bentRed, config.bentGreen, config.bentBlue, config.bentScale); + _scatteringResource->setBentNormalFactors(bentInfo); + + glm::vec2 curvatureInfo(config.curvatureOffset, config.curvatureScale); + _scatteringResource->setCurvatureFactors(curvatureInfo); + + _scatteringResource->setLevel((float)config.enableScattering); + _scatteringResource->setShowBRDF(config.showScatteringBRDF); + _scatteringResource->setShowCurvature(config.showCurvature); + _scatteringResource->setShowDiffusedNormal(config.showDiffusedNormal); +} + +void SubsurfaceScattering::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, Outputs& outputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + if (!_scatteringResource->getScatteringTable()) { + _scatteringResource->generateScatteringTable(renderContext->args); + } + + outputs = _scatteringResource; +} + +#ifdef GENERATE_SCATTERING_RESOURCE_ON_CPU + +// Reference: http://www.altdevblogaday.com/2011/12/31/skin-shading-in-unity3d/ +#include +#include +#include + +#define _PI 3.14159265358979523846 + +using namespace std; + +double gaussian(float v, float r) { + double g = (1.0 / sqrt(2.0 * _PI * v)) * exp(-(r*r) / (2.0 * v)); + return g; +} + +vec3 scatter(double r) { + // Values from GPU Gems 3 "Advanced Skin Rendering". + // Originally taken from real life samples. + static const double profile[][4] = { + { 0.0064, 0.233, 0.455, 0.649 }, + { 0.0484, 0.100, 0.336, 0.344 }, + { 0.1870, 0.118, 0.198, 0.000 }, + { 0.5670, 0.113, 0.007, 0.007 }, + { 1.9900, 0.358, 0.004, 0.000 }, + { 7.4100, 0.078, 0.000, 0.000 } + }; + static const int profileNum = 6; + vec3 ret(0.0); + for (int i = 0; i < profileNum; i++) { + double g = gaussian(profile[i][0] * 1.414f, r); + ret.x += g * profile[i][1]; + ret.y += g * profile[i][2]; + ret.z += g * profile[i][3]; + } + + return ret; +} + +vec3 integrate(double cosTheta, double skinRadius) { + // Angle from lighting direction. + double theta = acos(cosTheta); + vec3 totalWeights(0.0); + vec3 totalLight(0.0); + vec3 skinColour(1.0); + + double a = -(_PI); + + double inc = 0.005; + + while (a <= (_PI)) { + double sampleAngle = theta + a; + double diffuse = cos(sampleAngle); + if (diffuse < 0.0) diffuse = 0.0; + if (diffuse > 1.0) diffuse = 1.0; + + // Distance. + double sampleDist = abs(2.0 * skinRadius * sin(a * 0.5)); + + // Profile Weight. + vec3 weights = scatter(sampleDist); + + totalWeights += weights; + totalLight.x += diffuse * weights.x * (skinColour.x * skinColour.x); + totalLight.y += diffuse * weights.y * (skinColour.y * skinColour.y); + totalLight.z += diffuse * weights.z * (skinColour.z * skinColour.z); + a += inc; + } + + vec3 result; + result.x = totalLight.x / totalWeights.x; + result.y = totalLight.y / totalWeights.y; + result.z = totalLight.z / totalWeights.z; + + return result; +} + +void diffuseScatter(gpu::TexturePointer& lut) { + int width = lut->getWidth(); + int height = lut->getHeight(); + + const int COMPONENT_COUNT = 4; + std::vector bytes(COMPONENT_COUNT * height * width); + + int index = 0; + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + // Lookup by: x: NDotL y: 1 / r + float y = 2.0 * 1.0 / ((j + 1.0) / (double)height); + float x = ((i / (double)width) * 2.0) - 1.0; + vec3 val = integrate(x, y); + + // Convert to linear + // val.x = sqrt(val.x); + // val.y = sqrt(val.y); + // val.z = sqrt(val.z); + + // Convert to 24-bit image. + unsigned char valI[3]; + if (val.x > 1.0) val.x = 1.0; + if (val.y > 1.0) val.y = 1.0; + if (val.z > 1.0) val.z = 1.0; + valI[0] = (unsigned char)(val.x * 256.0); + valI[1] = (unsigned char)(val.y * 256.0); + valI[2] = (unsigned char)(val.z * 256.0); + + bytes[COMPONENT_COUNT * index] = valI[0]; + bytes[COMPONENT_COUNT * index + 1] = valI[1]; + bytes[COMPONENT_COUNT * index + 2] = valI[2]; + bytes[COMPONENT_COUNT * index + 3] = 255.0; + + index++; + } + } + + lut->assignStoredMip(0, gpu::Element::COLOR_RGBA_32, bytes.size(), bytes.data()); +} + + +void diffuseProfile(gpu::TexturePointer& profile) { + int width = profile->getWidth(); + int height = profile->getHeight(); + + const int COMPONENT_COUNT = 4; + std::vector bytes(COMPONENT_COUNT * height * width); + + int index = 0; + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + float y = (double)(i + 1.0) / (double)width; + vec3 val = scatter(y * 2.0f); + + // Convert to 24-bit image. + unsigned char valI[3]; + if (val.x > 1.0) val.x = 1.0; + if (val.y > 1.0) val.y = 1.0; + if (val.z > 1.0) val.z = 1.0; + valI[0] = (unsigned char)(val.x * 255.0); + valI[1] = (unsigned char)(val.y * 255.0); + valI[2] = (unsigned char)(val.z * 255.0); + + bytes[COMPONENT_COUNT * index] = valI[0]; + bytes[COMPONENT_COUNT * index + 1] = valI[1]; + bytes[COMPONENT_COUNT * index + 2] = valI[2]; + bytes[COMPONENT_COUNT * index + 3] = 255.0; + + index++; + } + } + + + profile->assignStoredMip(0, gpu::Element::COLOR_RGBA_32, bytes.size(), bytes.data()); +} + +#endif + +void diffuseProfileGPU(gpu::TexturePointer& profileMap, RenderArgs* args) { + int width = profileMap->getWidth(); + int height = profileMap->getHeight(); + + gpu::PipelinePointer makePipeline; + { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(subsurfaceScattering_makeProfile_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + makePipeline = gpu::Pipeline::create(program, state); + } + + auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + makeFramebuffer->setRenderBuffer(0, profileMap); + + gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + batch.enableStereo(false); + + batch.setViewportTransform(glm::ivec4(0, 0, width, height)); + + batch.setFramebuffer(makeFramebuffer); + batch.setPipeline(makePipeline); + batch.draw(gpu::TRIANGLE_STRIP, 4); + batch.setResourceTexture(0, nullptr); + batch.setPipeline(nullptr); + batch.setFramebuffer(nullptr); + }); +} + + +void diffuseScatterGPU(const gpu::TexturePointer& profileMap, gpu::TexturePointer& lut, RenderArgs* args) { + int width = lut->getWidth(); + int height = lut->getHeight(); + + gpu::PipelinePointer makePipeline; + { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(subsurfaceScattering_makeLUT_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("scatteringProfile"), 0)); + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + makePipeline = gpu::Pipeline::create(program, state); + } + + auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + makeFramebuffer->setRenderBuffer(0, lut); + + gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + batch.enableStereo(false); + + batch.setViewportTransform(glm::ivec4(0, 0, width, height)); + + batch.setFramebuffer(makeFramebuffer); + batch.setPipeline(makePipeline); + batch.setResourceTexture(0, profileMap); + batch.draw(gpu::TRIANGLE_STRIP, 4); + batch.setResourceTexture(0, nullptr); + batch.setPipeline(nullptr); + batch.setFramebuffer(nullptr); + + }); +} + +void computeSpecularBeckmannGPU(gpu::TexturePointer& beckmannMap, RenderArgs* args) { + int width = beckmannMap->getWidth(); + int height = beckmannMap->getHeight(); + + gpu::PipelinePointer makePipeline; + { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(subsurfaceScattering_makeSpecularBeckmann_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + makePipeline = gpu::Pipeline::create(program, state); + } + + auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + makeFramebuffer->setRenderBuffer(0, beckmannMap); + + gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + batch.enableStereo(false); + + batch.setViewportTransform(glm::ivec4(0, 0, width, height)); + + batch.setFramebuffer(makeFramebuffer); + batch.setPipeline(makePipeline); + batch.draw(gpu::TRIANGLE_STRIP, 4); + batch.setResourceTexture(0, nullptr); + batch.setPipeline(nullptr); + batch.setFramebuffer(nullptr); + }); +} + +gpu::TexturePointer SubsurfaceScatteringResource::generateScatteringProfile(RenderArgs* args) { + const int PROFILE_RESOLUTION = 512; + // const auto pixelFormat = gpu::Element::COLOR_SRGBA_32; + const auto pixelFormat = gpu::Element::COLOR_R11G11B10; + auto profileMap = gpu::TexturePointer(gpu::Texture::create2D(pixelFormat, PROFILE_RESOLUTION, 1, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); + diffuseProfileGPU(profileMap, args); + return profileMap; +} + +gpu::TexturePointer SubsurfaceScatteringResource::generatePreIntegratedScattering(const gpu::TexturePointer& profile, RenderArgs* args) { + + const int TABLE_RESOLUTION = 512; + // const auto pixelFormat = gpu::Element::COLOR_SRGBA_32; + const auto pixelFormat = gpu::Element::COLOR_R11G11B10; + auto scatteringLUT = gpu::TexturePointer(gpu::Texture::create2D(pixelFormat, TABLE_RESOLUTION, TABLE_RESOLUTION, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); + //diffuseScatter(scatteringLUT); + diffuseScatterGPU(profile, scatteringLUT, args); + return scatteringLUT; +} + +gpu::TexturePointer SubsurfaceScatteringResource::generateScatteringSpecularBeckmann(RenderArgs* args) { + const int SPECULAR_RESOLUTION = 256; + auto beckmannMap = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32 /*gpu::Element(gpu::SCALAR, gpu::HALF, gpu::RGB)*/, SPECULAR_RESOLUTION, SPECULAR_RESOLUTION, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); + computeSpecularBeckmannGPU(beckmannMap, args); + return beckmannMap; +} + +DebugSubsurfaceScattering::DebugSubsurfaceScattering() { +} + +void DebugSubsurfaceScattering::configure(const Config& config) { + + _showProfile = config.showProfile; + _showLUT = config.showLUT; + _showSpecularTable = config.showSpecularTable; + _showCursorPixel = config.showCursorPixel; + _debugCursorTexcoord = config.debugCursorTexcoord; +} + + + +gpu::PipelinePointer DebugSubsurfaceScattering::getScatteringPipeline() { + if (!_scatteringPipeline) { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(subsurfaceScattering_drawScattering_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ScatteringTask_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("scatteringParamsBuffer"), ScatteringTask_ParamSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), ScatteringTask_LightSlot)); + + slotBindings.insert(gpu::Shader::Binding(std::string("scatteringLUT"), ScatteringTask_ScatteringTableSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("curvatureMap"), ScatteringTask_CurvatureMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("diffusedCurvatureMap"), ScatteringTask_DiffusedCurvatureMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), ScatteringTask_NormalMapSlot)); + + slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), ScatteringTask_AlbedoMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("linearDepthMap"), ScatteringTask_LinearMapSlot)); + + slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), ScatteringTask_IBLMapSlot)); + + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + _scatteringPipeline = gpu::Pipeline::create(program, state); + } + + return _scatteringPipeline; +} + + +gpu::PipelinePointer _showLUTPipeline; +gpu::PipelinePointer getShowLUTPipeline(); +gpu::PipelinePointer DebugSubsurfaceScattering::getShowLUTPipeline() { + if (!_showLUTPipeline) { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::StandardShaderLib::getDrawTextureOpaquePS(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + _showLUTPipeline = gpu::Pipeline::create(program, state); + } + + return _showLUTPipeline; +} + + +void DebugSubsurfaceScattering::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + RenderArgs* args = renderContext->args; + + + auto& frameTransform = inputs.get0(); + auto& deferredFramebuffer = inputs.get1(); + + auto& surfaceGeometryFramebuffer = inputs.get3(); + auto curvatureFramebuffer = surfaceGeometryFramebuffer->getCurvatureFramebuffer(); + auto linearDepthTexture = surfaceGeometryFramebuffer->getLinearDepthTexture(); + + auto& diffusedFramebuffer = inputs.get4(); + auto& scatteringResource = inputs.get5(); + + if (!scatteringResource) { + return; + } + auto scatteringProfile = scatteringResource->getScatteringProfile(); + auto scatteringTable = scatteringResource->getScatteringTable(); + auto scatteringSpecular = scatteringResource->getScatteringSpecular(); + + + + + const auto theLight = DependencyManager::get()->getLightStage().lights[0]; + + gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + batch.enableStereo(false); + + + auto viewportSize = std::min(args->_viewport.z, args->_viewport.w) >> 1; + auto offsetViewport = viewportSize * 0.1; + + if (_showProfile) { + batch.setViewportTransform(glm::ivec4(0, 0, viewportSize, offsetViewport)); + batch.setPipeline(getShowLUTPipeline()); + batch.setResourceTexture(0, scatteringProfile); + batch.draw(gpu::TRIANGLE_STRIP, 4); + } + + if (_showLUT) { + batch.setViewportTransform(glm::ivec4(0, offsetViewport * 1.5, viewportSize, viewportSize)); + batch.setPipeline(getShowLUTPipeline()); + batch.setResourceTexture(0, scatteringTable); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + if (_showCursorPixel) { + + auto debugScatteringPipeline = getScatteringPipeline(); + batch.setPipeline(debugScatteringPipeline); + + Transform model; + model.setTranslation(glm::vec3(0.0, offsetViewport * 1.5 / args->_viewport.w, 0.0)); + model.setScale(glm::vec3(viewportSize / (float)args->_viewport.z, viewportSize / (float)args->_viewport.w, 1.0)); + batch.setModelTransform(model); + + batch.setUniformBuffer(ScatteringTask_FrameTransformSlot, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(ScatteringTask_ParamSlot, scatteringResource->getParametersBuffer()); + if (theLight->light) { + batch.setUniformBuffer(ScatteringTask_LightSlot, theLight->light->getSchemaBuffer()); + } + batch.setResourceTexture(ScatteringTask_ScatteringTableSlot, scatteringTable); + batch.setResourceTexture(ScatteringTask_CurvatureMapSlot, curvatureFramebuffer->getRenderBuffer(0)); + batch.setResourceTexture(ScatteringTask_DiffusedCurvatureMapSlot, diffusedFramebuffer->getRenderBuffer(0)); + batch.setResourceTexture(ScatteringTask_NormalMapSlot, deferredFramebuffer->getDeferredNormalTexture()); + batch.setResourceTexture(ScatteringTask_AlbedoMapSlot, deferredFramebuffer->getDeferredColorTexture()); + batch.setResourceTexture(ScatteringTask_LinearMapSlot, linearDepthTexture); + + + batch._glUniform2f(debugScatteringPipeline->getProgram()->getUniforms().findLocation("uniformCursorTexcoord"), _debugCursorTexcoord.x, _debugCursorTexcoord.y); + batch.draw(gpu::TRIANGLE_STRIP, 4); + } + } + + if (_showSpecularTable) { + batch.setViewportTransform(glm::ivec4(viewportSize + offsetViewport * 0.5, 0, viewportSize * 0.5, viewportSize * 0.5)); + batch.setPipeline(getShowLUTPipeline()); + batch.setResourceTexture(0, scatteringSpecular); + batch.draw(gpu::TRIANGLE_STRIP, 4); + } + + batch.setViewportTransform(args->_viewport); + + }); +} diff --git a/libraries/render-utils/src/SubsurfaceScattering.h b/libraries/render-utils/src/SubsurfaceScattering.h new file mode 100644 index 0000000000..715d9bc77b --- /dev/null +++ b/libraries/render-utils/src/SubsurfaceScattering.h @@ -0,0 +1,189 @@ +// +// SubsurfaceScattering.h +// libraries/render-utils/src/ +// +// Created by Sam Gateau 6/3/2016. +// Copyright 2016 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_SubsurfaceScattering_h +#define hifi_SubsurfaceScattering_h + +#include + +#include "render/DrawTask.h" +#include "DeferredFrameTransform.h" +#include "DeferredFramebuffer.h" +#include "SurfaceGeometryPass.h" +#include "LightingModel.h" + +class SubsurfaceScatteringResource { +public: + using UniformBufferView = gpu::BufferView; + + SubsurfaceScatteringResource(); + + void setBentNormalFactors(const glm::vec4& rgbsBentFactors); + glm::vec4 getBentNormalFactors() const; + + void setCurvatureFactors(const glm::vec2& sbCurvatureFactors); + glm::vec2 getCurvatureFactors() const; + + void setLevel(float level); + float getLevel() const; + + + void setShowBRDF(bool show); + bool isShowBRDF() const; + void setShowCurvature(bool show); + bool isShowCurvature() const; + void setShowDiffusedNormal(bool show); + bool isShowDiffusedNormal() const; + + UniformBufferView getParametersBuffer() const { return _parametersBuffer; } + + gpu::TexturePointer getScatteringProfile() const { return _scatteringProfile; } + gpu::TexturePointer getScatteringTable() const { return _scatteringTable; } + gpu::TexturePointer getScatteringSpecular() const { return _scatteringSpecular; } + + void generateScatteringTable(RenderArgs* args); + + static gpu::TexturePointer generateScatteringProfile(RenderArgs* args); + static gpu::TexturePointer generatePreIntegratedScattering(const gpu::TexturePointer& profile, RenderArgs* args); + static gpu::TexturePointer generateScatteringSpecularBeckmann(RenderArgs* args); + +protected: + + + // Class describing the uniform buffer with the transform info common to the AO shaders + // It s changing every frame + class Parameters { + public: + glm::vec4 normalBentInfo{ 1.5f, 0.8f, 0.3f, 1.5f }; + glm::vec2 curvatureInfo{ 0.08f, 0.8f }; + float level{ 1.0f }; + float showBRDF{ 0.0f }; + float showCurvature{ 0.0f }; + float showDiffusedNormal{ 0.0f }; + float spare1{ 0.0f }; + float spare2{ 0.0f }; + + + Parameters() {} + }; + UniformBufferView _parametersBuffer; + + + + gpu::TexturePointer _scatteringProfile; + gpu::TexturePointer _scatteringTable; + gpu::TexturePointer _scatteringSpecular; +}; + +using SubsurfaceScatteringResourcePointer = std::shared_ptr; + + + +class SubsurfaceScatteringConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float bentRed MEMBER bentRed NOTIFY dirty) + Q_PROPERTY(float bentGreen MEMBER bentGreen NOTIFY dirty) + Q_PROPERTY(float bentBlue MEMBER bentBlue NOTIFY dirty) + Q_PROPERTY(float bentScale MEMBER bentScale NOTIFY dirty) + + Q_PROPERTY(float curvatureOffset MEMBER curvatureOffset NOTIFY dirty) + Q_PROPERTY(float curvatureScale MEMBER curvatureScale NOTIFY dirty) + + Q_PROPERTY(bool enableScattering MEMBER enableScattering NOTIFY dirty) + Q_PROPERTY(bool showScatteringBRDF MEMBER showScatteringBRDF NOTIFY dirty) + Q_PROPERTY(bool showCurvature MEMBER showCurvature NOTIFY dirty) + Q_PROPERTY(bool showDiffusedNormal MEMBER showDiffusedNormal NOTIFY dirty) + +public: + SubsurfaceScatteringConfig() : render::Job::Config(true) {} + + float bentRed{ 1.5f }; + float bentGreen{ 0.8f }; + float bentBlue{ 0.3f }; + float bentScale{ 1.5f }; + + float curvatureOffset{ 0.08f }; + float curvatureScale{ 0.9f }; + + bool enableScattering{ true }; + bool showScatteringBRDF{ false }; + bool showCurvature{ false }; + bool showDiffusedNormal{ false }; + +signals: + void dirty(); +}; + +class SubsurfaceScattering { +public: + //using Inputs = render::VaryingSet4; + using Outputs = SubsurfaceScatteringResourcePointer; + using Config = SubsurfaceScatteringConfig; + using JobModel = render::Job::ModelO; + + SubsurfaceScattering(); + + void configure(const Config& config); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, Outputs& outputs); + +private: + SubsurfaceScatteringResourcePointer _scatteringResource; +}; + + + +class DebugSubsurfaceScatteringConfig : public render::Job::Config { + Q_OBJECT + + Q_PROPERTY(bool showProfile MEMBER showProfile NOTIFY dirty) + Q_PROPERTY(bool showLUT MEMBER showLUT NOTIFY dirty) + Q_PROPERTY(bool showSpecularTable MEMBER showSpecularTable NOTIFY dirty) + Q_PROPERTY(bool showCursorPixel MEMBER showCursorPixel NOTIFY dirty) + Q_PROPERTY(glm::vec2 debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty) +public: + DebugSubsurfaceScatteringConfig() : render::Job::Config(true) {} + + bool showProfile{ false }; + bool showLUT{ false }; + bool showSpecularTable{ false }; + bool showCursorPixel{ false }; + glm::vec2 debugCursorTexcoord{ 0.5, 0.5 }; + +signals: + void dirty(); +}; + +class DebugSubsurfaceScattering { +public: + using Inputs = render::VaryingSet6; + using Config = DebugSubsurfaceScatteringConfig; + using JobModel = render::Job::ModelI; + + DebugSubsurfaceScattering(); + + void configure(const Config& config); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs); + +private: + + gpu::PipelinePointer _scatteringPipeline; + gpu::PipelinePointer getScatteringPipeline(); + + gpu::PipelinePointer _showLUTPipeline; + gpu::PipelinePointer getShowLUTPipeline(); + bool _showProfile{ false }; + bool _showLUT{ false }; + bool _showSpecularTable{ false }; + bool _showCursorPixel{ false }; + glm::vec2 _debugCursorTexcoord; +}; + +#endif // hifi_SubsurfaceScattering_h diff --git a/libraries/render-utils/src/SubsurfaceScattering.slh b/libraries/render-utils/src/SubsurfaceScattering.slh new file mode 100644 index 0000000000..6d5dd01d8f --- /dev/null +++ b/libraries/render-utils/src/SubsurfaceScattering.slh @@ -0,0 +1,226 @@ +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/8/16. +// Copyright 2016 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 +// +<@if not SUBSURFACE_SCATTERING_SLH@> +<@def SUBSURFACE_SCATTERING_SLH@> + +<@func declareSubsurfaceScatteringProfileSource()@> + +float gaussian(float v, float r) { + const float _PI = 3.14159265358979523846; + return (1.0 / sqrt(2.0 * _PI * v)) * exp(-(r*r) / (2.0 * v)); +} + +vec3 scatter(float r) { + // r is the distance expressed in millimeter + // returns the scatter reflectance + // Values from GPU Gems 3 "Advanced Skin Rendering". + // Originally taken from real life samples. + const vec4 profile[6] = vec4[6]( + vec4(0.0064, 0.233, 0.455, 0.649), + vec4(0.0484, 0.100, 0.336, 0.344), + vec4(0.1870, 0.118, 0.198, 0.000), + vec4(0.5670, 0.113, 0.007, 0.007), + vec4(1.9900, 0.358, 0.004, 0.000), + vec4(7.4100, 0.078, 0.000, 0.000) + ); + const int profileNum = 6; + + vec3 ret = vec3(0.0); + for (int i = 0; i < profileNum; i++) { + float v = profile[i].x * 1.414; + float g = gaussian(v, r); + ret += g * profile[i].yzw; + } + + return ret; +} + +<@endfunc@> + +<@func declareSubsurfaceScatteringGenerateProfileMap()@> +<$declareSubsurfaceScatteringProfileSource()$> + +vec3 generateProfile(vec2 uv) { + return scatter(uv.x * 2.0); +} + +<@endfunc@> + +<@func declareSubsurfaceScatteringProfileMap()@> + +uniform sampler2D scatteringProfile; + +vec3 scatter(float r) { + return texture(scatteringProfile, vec2(r * 0.5, 0.5)).rgb; +} + +<@endfunc@> + + +<@func declareSkinSpecularLighting()@> + +uniform sampler2D scatteringSpecularBeckmann; + +float fetchSpecularBeckmann(float ndoth, float roughness) { + return pow( 2.0 * texture(scatteringSpecularBeckmann, vec2(ndoth, roughness)).r, 10.0); +} + +float fresnelReflectance(vec3 H, vec3 V, float Fo) { + float base = 1.0 - dot(V, H); + float exponential = pow(base, 5.0); + return exponential + Fo * (1.0 - exponential); +} + +float skinSpecular(vec3 N, vec3 L, vec3 V, float roughness, float intensity) { + float result = 0.0; + float ndotl = dot(N, L); + if (ndotl > 0.0) { + vec3 h = L + V; + vec3 H = normalize(h); + float ndoth = dot(N, H); + float PH = fetchSpecularBeckmann(ndoth, roughness); + float F = fresnelReflectance(H, V, 0.028); + float frSpec = max(PH * F / dot(h, h), 0.0); + result = ndotl * intensity * frSpec; + } + + return result; +} + +<@endfunc@> + +<@func declareSubsurfaceScatteringIntegrate(NumIntegrationSteps)@> + + +vec3 integrate(float cosTheta, float skinRadius) { + // Angle from lighting direction. + float theta = acos(cosTheta); + vec3 totalWeights = vec3(0.0); + vec3 totalLight = vec3(0.0); + + const float _PI = 3.14159265358979523846; + const float step = 2.0 * _PI / <$NumIntegrationSteps$>; + float a = -(_PI); + + + while (a <= (_PI)) { + float sampleAngle = theta + a; + float diffuse = clamp(cos(sampleAngle), 0.0, 1.0); + //if (diffuse < 0.0) diffuse = 0.0; + //if (diffuse > 1.0) diffuse = 1.0; + + // Distance. + float sampleDist = abs(2.0 * skinRadius * sin(a * 0.5)); + + // Profile Weight. + vec3 weights = scatter(sampleDist); + + totalWeights += weights; + totalLight += diffuse * weights; + a += step; + } + + vec3 result = (totalLight / totalWeights); + return clamp(result, vec3(0.0), vec3(1.0)); + +} +<@endfunc@> + +<@func declareSubsurfaceScatteringResource()@> + +uniform sampler2D scatteringLUT; + +vec3 fetchBRDF(float LdotN, float curvature) { + return texture(scatteringLUT, vec2( clamp(LdotN * 0.5 + 0.5, 0.0, 1.0), clamp(2 * curvature, 0.0, 1.0))).xyz; +} + +vec3 fetchBRDFSpectrum(vec3 LdotNSpectrum, float curvature) { + return vec3( + fetchBRDF(LdotNSpectrum.r, curvature).r, + fetchBRDF(LdotNSpectrum.g, curvature).g, + fetchBRDF(LdotNSpectrum.b, curvature).b); +} + +// Subsurface Scattering parameters +struct ScatteringParameters { + vec4 normalBendInfo; // R, G, B, factor + vec4 curvatureInfo;// Offset, Scale, level + vec4 debugFlags; +}; + +uniform subsurfaceScatteringParametersBuffer { + ScatteringParameters parameters; +}; + +vec3 getBendFactor() { + return parameters.normalBendInfo.xyz * parameters.normalBendInfo.w; +} + +float getScatteringLevel() { + return parameters.curvatureInfo.z; +} + +bool showBRDF() { + return parameters.curvatureInfo.w > 0.0; +} + +bool showCurvature() { + return parameters.debugFlags.x > 0.0; +} +bool showDiffusedNormal() { + return parameters.debugFlags.y > 0.0; +} + + +float tuneCurvatureUnsigned(float curvature) { + return abs(curvature) * parameters.curvatureInfo.y + parameters.curvatureInfo.x; +} + +float unpackCurvature(float packedCurvature) { + return (packedCurvature * 2 - 1); +} + +vec3 evalScatteringBentNdotL(vec3 normal, vec3 midNormal, vec3 lowNormal, vec3 lightDir) { + vec3 bendFactorSpectrum = getBendFactor(); + // vec3 rN = normalize(mix(normal, lowNormal, bendFactorSpectrum.x)); + vec3 rN = normalize(mix(midNormal, lowNormal, bendFactorSpectrum.x)); + vec3 gN = normalize(mix(midNormal, lowNormal, bendFactorSpectrum.y)); + vec3 bN = normalize(mix(midNormal, lowNormal, bendFactorSpectrum.z)); + + vec3 NdotLSpectrum = vec3(dot(rN, lightDir), dot(gN, lightDir), dot(bN, lightDir)); + + return NdotLSpectrum; +} + + + +<@endfunc@> + +<@func declareSubsurfaceScatteringBRDF()@> +<$declareSubsurfaceScatteringResource()$> + +vec3 evalSkinBRDF(vec3 lightDir, vec3 normal, vec3 midNormal, vec3 lowNormal, float curvature) { + if (showDiffusedNormal()) { + return lowNormal * 0.5 + vec3(0.5); + } + if (showCurvature()) { + return (curvature > 0 ? vec3(curvature, 0.0, 0.0) : vec3(0.0, 0.0, -curvature)); + } + + vec3 bentNdotL = evalScatteringBentNdotL(normal, midNormal, lowNormal, lightDir); + + float tunedCurvature = tuneCurvatureUnsigned(curvature); + + vec3 brdf = fetchBRDFSpectrum(bentNdotL, tunedCurvature); + return brdf; +} + +<@endfunc@> + +<@endif@> \ No newline at end of file diff --git a/libraries/render-utils/src/SurfaceGeometry.slh b/libraries/render-utils/src/SurfaceGeometry.slh new file mode 100644 index 0000000000..5dd5b8128e --- /dev/null +++ b/libraries/render-utils/src/SurfaceGeometry.slh @@ -0,0 +1,16 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/3/16. +// Copyright 2016 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 DeferredTransform.slh@> +<$declareDeferredFrameTransform()$> + + + diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp new file mode 100644 index 0000000000..fd778d30be --- /dev/null +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -0,0 +1,553 @@ +// +// SurfaceGeometryPass.cpp +// libraries/render-utils/src/ +// +// Created by Sam Gateau 6/3/2016. +// Copyright 2016 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 "SurfaceGeometryPass.h" + +#include + +#include +#include + + +const int DepthLinearPass_FrameTransformSlot = 0; +const int DepthLinearPass_DepthMapSlot = 0; +const int DepthLinearPass_NormalMapSlot = 1; + +const int SurfaceGeometryPass_FrameTransformSlot = 0; +const int SurfaceGeometryPass_ParamsSlot = 1; +const int SurfaceGeometryPass_DepthMapSlot = 0; +const int SurfaceGeometryPass_NormalMapSlot = 1; + +#include "surfaceGeometry_makeLinearDepth_frag.h" +#include "surfaceGeometry_downsampleDepthNormal_frag.h" + +#include "surfaceGeometry_makeCurvature_frag.h" + + + +LinearDepthFramebuffer::LinearDepthFramebuffer() { +} + + +void LinearDepthFramebuffer::updatePrimaryDepth(const gpu::TexturePointer& depthBuffer) { + //If the depth buffer or size changed, we need to delete our FBOs + bool reset = false; + if ((_primaryDepthTexture != depthBuffer)) { + _primaryDepthTexture = depthBuffer; + reset = true; + } + if (_primaryDepthTexture) { + auto newFrameSize = glm::ivec2(_primaryDepthTexture->getDimensions()); + if (_frameSize != newFrameSize) { + _frameSize = newFrameSize; + _halfFrameSize = newFrameSize >> 1; + + reset = true; + } + } + + if (reset) { + clear(); + } +} + +void LinearDepthFramebuffer::clear() { + _linearDepthFramebuffer.reset(); + _linearDepthTexture.reset(); +} + +void LinearDepthFramebuffer::allocate() { + + auto width = _frameSize.x; + auto height = _frameSize.y; + + // For Linear Depth: + _linearDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), width, height, + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + // _linearDepthTexture->autoGenerateMips(1); + _linearDepthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + _linearDepthFramebuffer->setRenderBuffer(0, _linearDepthTexture); + _linearDepthFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, _primaryDepthTexture->getTexelFormat()); + + // For Downsampling: + _halfLinearDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), _halfFrameSize.x, _halfFrameSize.y, + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + + _halfNormalTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB), _halfFrameSize.x, _halfFrameSize.y, + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + + _downsampleFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + _downsampleFramebuffer->setRenderBuffer(0, _halfLinearDepthTexture); + _downsampleFramebuffer->setRenderBuffer(1, _halfNormalTexture); +} + +gpu::FramebufferPointer LinearDepthFramebuffer::getLinearDepthFramebuffer() { + if (!_linearDepthFramebuffer) { + allocate(); + } + return _linearDepthFramebuffer; +} + +gpu::TexturePointer LinearDepthFramebuffer::getLinearDepthTexture() { + if (!_linearDepthTexture) { + allocate(); + } + return _linearDepthTexture; +} + +gpu::FramebufferPointer LinearDepthFramebuffer::getDownsampleFramebuffer() { + if (!_downsampleFramebuffer) { + allocate(); + } + return _downsampleFramebuffer; +} + +gpu::TexturePointer LinearDepthFramebuffer::getHalfLinearDepthTexture() { + if (!_halfLinearDepthTexture) { + allocate(); + } + return _halfLinearDepthTexture; +} + +gpu::TexturePointer LinearDepthFramebuffer::getHalfNormalTexture() { + if (!_halfNormalTexture) { + allocate(); + } + return _halfNormalTexture; +} + + +LinearDepthPass::LinearDepthPass() { +} + +void LinearDepthPass::configure(const Config& config) { +} + +void LinearDepthPass::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + RenderArgs* args = renderContext->args; + + const auto frameTransform = inputs.get0(); + const auto deferredFramebuffer = inputs.get1(); + + if (!_linearDepthFramebuffer) { + _linearDepthFramebuffer = std::make_shared(); + } + _linearDepthFramebuffer->updatePrimaryDepth(deferredFramebuffer->getPrimaryDepthTexture()); + + auto depthBuffer = deferredFramebuffer->getPrimaryDepthTexture(); + auto normalTexture = deferredFramebuffer->getDeferredNormalTexture(); + + auto linearDepthFBO = _linearDepthFramebuffer->getLinearDepthFramebuffer(); + auto linearDepthTexture = _linearDepthFramebuffer->getLinearDepthTexture(); + + auto downsampleFBO = _linearDepthFramebuffer->getDownsampleFramebuffer(); + auto halfLinearDepthTexture = _linearDepthFramebuffer->getHalfLinearDepthTexture(); + auto halfNormalTexture = _linearDepthFramebuffer->getHalfNormalTexture(); + + outputs.edit0() = _linearDepthFramebuffer; + outputs.edit1() = linearDepthFBO; + outputs.edit2() = linearDepthTexture; + outputs.edit3() = halfLinearDepthTexture; + outputs.edit4() = halfNormalTexture; + + auto linearDepthPipeline = getLinearDepthPipeline(); + auto downsamplePipeline = getDownsamplePipeline(); + + auto depthViewport = args->_viewport; + auto halfViewport = depthViewport >> 1; + float clearLinearDepth = args->getViewFrustum().getFarClip() * 2.0f; + + gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + _gpuTimer.begin(batch); + batch.enableStereo(false); + + batch.setViewportTransform(depthViewport); + batch.setProjectionTransform(glm::mat4()); + batch.setViewTransform(Transform()); + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_linearDepthFramebuffer->getDepthFrameSize(), depthViewport)); + + batch.setUniformBuffer(DepthLinearPass_FrameTransformSlot, frameTransform->getFrameTransformBuffer()); + + // LinearDepth + batch.setFramebuffer(linearDepthFBO); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(clearLinearDepth, 0.0f, 0.0f, 0.0f)); + batch.setPipeline(linearDepthPipeline); + batch.setResourceTexture(DepthLinearPass_DepthMapSlot, depthBuffer); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + // Downsample + batch.setViewportTransform(halfViewport); + + batch.setFramebuffer(downsampleFBO); + batch.setResourceTexture(DepthLinearPass_DepthMapSlot, linearDepthTexture); + batch.setResourceTexture(DepthLinearPass_NormalMapSlot, normalTexture); + batch.setPipeline(downsamplePipeline); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + _gpuTimer.end(batch); + }); + + auto config = std::static_pointer_cast(renderContext->jobConfig); + config->gpuTime = _gpuTimer.getAverage(); +} + + +const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline() { + if (!_linearDepthPipeline) { + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_makeLinearDepth_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DepthLinearPass_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), DepthLinearPass_DepthMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + // Stencil test the curvature pass for objects pixels only, not the background + state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + + state->setColorWriteMask(true, false, false, false); + + // Good to go add the brand new pipeline + _linearDepthPipeline = gpu::Pipeline::create(program, state); + } + + return _linearDepthPipeline; +} + + +const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline() { + if (!_downsamplePipeline) { + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_downsampleDepthNormal_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DepthLinearPass_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("linearDepthMap"), DepthLinearPass_DepthMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), DepthLinearPass_NormalMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + state->setColorWriteMask(true, true, true, false); + + // Good to go add the brand new pipeline + _downsamplePipeline = gpu::Pipeline::create(program, state); + } + + return _downsamplePipeline; +} + + +//#define USE_STENCIL_TEST + +SurfaceGeometryFramebuffer::SurfaceGeometryFramebuffer() { +} + +void SurfaceGeometryFramebuffer::updateLinearDepth(const gpu::TexturePointer& linearDepthBuffer) { + //If the depth buffer or size changed, we need to delete our FBOs + bool reset = false; + if ((_linearDepthTexture != linearDepthBuffer)) { + _linearDepthTexture = linearDepthBuffer; + reset = true; + } + if (_linearDepthTexture) { + auto newFrameSize = glm::ivec2(_linearDepthTexture->getDimensions()); + if (_frameSize != newFrameSize) { + _frameSize = newFrameSize; + reset = true; + } + } + + if (reset) { + clear(); + } +} + +void SurfaceGeometryFramebuffer::clear() { + _curvatureFramebuffer.reset(); + _curvatureTexture.reset(); + _lowCurvatureFramebuffer.reset(); + _lowCurvatureTexture.reset(); + _blurringFramebuffer.reset(); + _blurringTexture.reset(); +} + +gpu::TexturePointer SurfaceGeometryFramebuffer::getLinearDepthTexture() { + return _linearDepthTexture; +} + +void SurfaceGeometryFramebuffer::allocate() { + + auto width = _frameSize.x; + auto height = _frameSize.y; + + _curvatureTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _curvatureFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + _curvatureFramebuffer->setRenderBuffer(0, _curvatureTexture); + + _lowCurvatureTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _lowCurvatureFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + _lowCurvatureFramebuffer->setRenderBuffer(0, _lowCurvatureTexture); + + _blurringTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _blurringFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + _blurringFramebuffer->setRenderBuffer(0, _blurringTexture); +} + +gpu::FramebufferPointer SurfaceGeometryFramebuffer::getCurvatureFramebuffer() { + if (!_curvatureFramebuffer) { + allocate(); + } + return _curvatureFramebuffer; +} + +gpu::TexturePointer SurfaceGeometryFramebuffer::getCurvatureTexture() { + if (!_curvatureTexture) { + allocate(); + } + return _curvatureTexture; +} + +gpu::FramebufferPointer SurfaceGeometryFramebuffer::getLowCurvatureFramebuffer() { + if (!_lowCurvatureFramebuffer) { + allocate(); + } + return _lowCurvatureFramebuffer; +} + +gpu::TexturePointer SurfaceGeometryFramebuffer::getLowCurvatureTexture() { + if (!_lowCurvatureTexture) { + allocate(); + } + return _lowCurvatureTexture; +} + +gpu::FramebufferPointer SurfaceGeometryFramebuffer::getBlurringFramebuffer() { + if (!_blurringFramebuffer) { + allocate(); + } + return _blurringFramebuffer; +} + +gpu::TexturePointer SurfaceGeometryFramebuffer::getBlurringTexture() { + if (!_blurringTexture) { + allocate(); + } + return _blurringTexture; +} + +void SurfaceGeometryFramebuffer::setResolutionLevel(int resolutionLevel) { + if (resolutionLevel != getResolutionLevel()) { + clear(); + _resolutionLevel = resolutionLevel; + } +} + +SurfaceGeometryPass::SurfaceGeometryPass() : + _diffusePass(false) +{ + Parameters parameters; + _parametersBuffer = gpu::BufferView(std::make_shared(sizeof(Parameters), (const gpu::Byte*) ¶meters)); +} + +void SurfaceGeometryPass::configure(const Config& config) { + const float CM_TO_M = 0.01f; + + if ((config.depthThreshold * CM_TO_M) != getCurvatureDepthThreshold()) { + _parametersBuffer.edit().curvatureInfo.x = config.depthThreshold * CM_TO_M; + } + + if (config.basisScale != getCurvatureBasisScale()) { + _parametersBuffer.edit().curvatureInfo.y = config.basisScale; + } + + if (config.curvatureScale != getCurvatureScale()) { + _parametersBuffer.edit().curvatureInfo.w = config.curvatureScale; + } + + if (!_surfaceGeometryFramebuffer) { + _surfaceGeometryFramebuffer = std::make_shared(); + } + + _surfaceGeometryFramebuffer->setResolutionLevel(config.resolutionLevel); + if (config.resolutionLevel != getResolutionLevel()) { + _parametersBuffer.edit().resolutionInfo.w = config.resolutionLevel; + } + + auto filterRadius = (getResolutionLevel() > 0 ? config.diffuseFilterScale / 2.0f : config.diffuseFilterScale); + _diffusePass.getParameters()->setFilterRadiusScale(filterRadius); + _diffusePass.getParameters()->setDepthThreshold(config.diffuseDepthThreshold); + +} + + +void SurfaceGeometryPass::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + RenderArgs* args = renderContext->args; + + const auto frameTransform = inputs.get0(); + const auto deferredFramebuffer = inputs.get1(); + const auto linearDepthFramebuffer = inputs.get2(); + + + auto linearDepthTexture = linearDepthFramebuffer->getLinearDepthTexture(); + auto normalTexture = deferredFramebuffer->getDeferredNormalTexture(); + auto sourceViewport = args->_viewport; + auto curvatureViewport = sourceViewport; + + if (_surfaceGeometryFramebuffer->getResolutionLevel() > 0) { + linearDepthTexture = linearDepthFramebuffer->getHalfLinearDepthTexture(); + normalTexture = linearDepthFramebuffer->getHalfNormalTexture(); + curvatureViewport = curvatureViewport >> _surfaceGeometryFramebuffer->getResolutionLevel(); + } + + if (!_surfaceGeometryFramebuffer) { + _surfaceGeometryFramebuffer = std::make_shared(); + } + _surfaceGeometryFramebuffer->updateLinearDepth(linearDepthTexture); + + auto curvatureFramebuffer = _surfaceGeometryFramebuffer->getCurvatureFramebuffer(); + auto curvatureTexture = _surfaceGeometryFramebuffer->getCurvatureTexture(); +#ifdef USE_STENCIL_TEST + if (curvatureFramebuffer->getDepthStencilBuffer() != deferredFramebuffer->getPrimaryDepthTexture()) { + curvatureFramebuffer->setDepthStencilBuffer(deferredFramebuffer->getPrimaryDepthTexture(), deferredFramebuffer->getPrimaryDepthTexture()->getTexelFormat()); + } +#endif + + auto lowCurvatureFramebuffer = _surfaceGeometryFramebuffer->getLowCurvatureFramebuffer(); + auto lowCurvatureTexture = _surfaceGeometryFramebuffer->getLowCurvatureTexture(); + + auto blurringFramebuffer = _surfaceGeometryFramebuffer->getBlurringFramebuffer(); + auto blurringTexture = _surfaceGeometryFramebuffer->getBlurringTexture(); + + outputs.edit0() = _surfaceGeometryFramebuffer; + outputs.edit1() = curvatureFramebuffer; + outputs.edit2() = curvatureFramebuffer; + outputs.edit3() = lowCurvatureFramebuffer; + + auto curvaturePipeline = getCurvaturePipeline(); + auto diffuseVPipeline = _diffusePass.getBlurVPipeline(); + auto diffuseHPipeline = _diffusePass.getBlurHPipeline(); + + _diffusePass.getParameters()->setWidthHeight(curvatureViewport.z, curvatureViewport.w, args->_context->isStereo()); + glm::ivec2 textureSize(curvatureTexture->getDimensions()); + _diffusePass.getParameters()->setTexcoordTransform(gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(textureSize, curvatureViewport)); + _diffusePass.getParameters()->setDepthPerspective(args->getViewFrustum().getProjection()[1][1]); + _diffusePass.getParameters()->setLinearDepthPosFar(args->getViewFrustum().getFarClip()); + + + gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + _gpuTimer.begin(batch); + batch.enableStereo(false); + + batch.setProjectionTransform(glm::mat4()); + batch.setViewTransform(Transform()); + + batch.setViewportTransform(curvatureViewport); + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_surfaceGeometryFramebuffer->getSourceFrameSize(), curvatureViewport)); + + // Curvature pass + batch.setUniformBuffer(SurfaceGeometryPass_FrameTransformSlot, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(SurfaceGeometryPass_ParamsSlot, _parametersBuffer); + batch.setFramebuffer(curvatureFramebuffer); + // We can avoid the clear by drawing the same clear vallue from the makeCurvature shader. same performances or no worse +#ifdef USE_STENCIL_TEST + // Except if stenciling out + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0)); +#endif + batch.setPipeline(curvaturePipeline); + batch.setResourceTexture(SurfaceGeometryPass_DepthMapSlot, linearDepthTexture); + batch.setResourceTexture(SurfaceGeometryPass_NormalMapSlot, normalTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + + batch.setResourceTexture(SurfaceGeometryPass_DepthMapSlot, nullptr); + batch.setResourceTexture(SurfaceGeometryPass_NormalMapSlot, nullptr); + batch.setUniformBuffer(SurfaceGeometryPass_ParamsSlot, nullptr); + batch.setUniformBuffer(SurfaceGeometryPass_FrameTransformSlot, nullptr); + + // Diffusion pass + const int BlurTask_ParamsSlot = 0; + const int BlurTask_SourceSlot = 0; + const int BlurTask_DepthSlot = 1; + batch.setUniformBuffer(BlurTask_ParamsSlot, _diffusePass.getParameters()->_parametersBuffer); + + batch.setResourceTexture(BlurTask_DepthSlot, linearDepthTexture); + + batch.setFramebuffer(blurringFramebuffer); + batch.setPipeline(diffuseVPipeline); + batch.setResourceTexture(BlurTask_SourceSlot, curvatureTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + batch.setFramebuffer(curvatureFramebuffer); + batch.setPipeline(diffuseHPipeline); + batch.setResourceTexture(BlurTask_SourceSlot, blurringTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + batch.setFramebuffer(blurringFramebuffer); + batch.setPipeline(diffuseVPipeline); + batch.setResourceTexture(BlurTask_SourceSlot, curvatureTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + batch.setFramebuffer(lowCurvatureFramebuffer); + batch.setPipeline(diffuseHPipeline); + batch.setResourceTexture(BlurTask_SourceSlot, blurringTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + batch.setResourceTexture(BlurTask_SourceSlot, nullptr); + batch.setResourceTexture(BlurTask_DepthSlot, nullptr); + batch.setUniformBuffer(BlurTask_ParamsSlot, nullptr); + + _gpuTimer.end(batch); + }); + + + auto config = std::static_pointer_cast(renderContext->jobConfig); + config->gpuTime = _gpuTimer.getAverage(); +} + + +const gpu::PipelinePointer& SurfaceGeometryPass::getCurvaturePipeline() { + if (!_curvaturePipeline) { + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_makeCurvature_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), SurfaceGeometryPass_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("surfaceGeometryParamsBuffer"), SurfaceGeometryPass_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), SurfaceGeometryPass_DepthMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), SurfaceGeometryPass_NormalMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + +#ifdef USE_STENCIL_TEST + // Stencil test the curvature pass for objects pixels only, not the background + state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); +#endif + // Good to go add the brand new pipeline + _curvaturePipeline = gpu::Pipeline::create(program, state); + } + + return _curvaturePipeline; +} diff --git a/libraries/render-utils/src/SurfaceGeometryPass.h b/libraries/render-utils/src/SurfaceGeometryPass.h new file mode 100644 index 0000000000..24f0c56cdd --- /dev/null +++ b/libraries/render-utils/src/SurfaceGeometryPass.h @@ -0,0 +1,226 @@ +// +// SurfaceGeometryPass.h +// libraries/render-utils/src/ +// +// Created by Sam Gateau 6/3/2016. +// Copyright 2016 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_SurfaceGeometryPass_h +#define hifi_SurfaceGeometryPass_h + +#include + +#include "render/DrawTask.h" +#include "render/BlurTask.h" +#include "DeferredFrameTransform.h" +#include "DeferredFramebuffer.h" + + +// SurfaceGeometryFramebuffer is a helper class gathering in one place theframebuffers and targets describing the surface geometry linear depth +// from a z buffer +class LinearDepthFramebuffer { +public: + LinearDepthFramebuffer(); + + gpu::FramebufferPointer getLinearDepthFramebuffer(); + gpu::TexturePointer getLinearDepthTexture(); + + gpu::FramebufferPointer getDownsampleFramebuffer(); + gpu::TexturePointer getHalfLinearDepthTexture(); + gpu::TexturePointer getHalfNormalTexture(); + + // Update the depth buffer which will drive the allocation of all the other resources according to its size. + void updatePrimaryDepth(const gpu::TexturePointer& depthBuffer); + gpu::TexturePointer getPrimaryDepthTexture(); + const glm::ivec2& getDepthFrameSize() const { return _frameSize; } + + void setResolutionLevel(int level); + int getResolutionLevel() const { return _resolutionLevel; } + +protected: + void clear(); + void allocate(); + + gpu::TexturePointer _primaryDepthTexture; + + gpu::FramebufferPointer _linearDepthFramebuffer; + gpu::TexturePointer _linearDepthTexture; + + gpu::FramebufferPointer _downsampleFramebuffer; + gpu::TexturePointer _halfLinearDepthTexture; + gpu::TexturePointer _halfNormalTexture; + + + glm::ivec2 _frameSize; + glm::ivec2 _halfFrameSize; + int _resolutionLevel{ 0 }; +}; + +using LinearDepthFramebufferPointer = std::shared_ptr; + + +class LinearDepthPassConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(double gpuTime READ getGpuTime) +public: + LinearDepthPassConfig() : render::Job::Config(true) {} + + double getGpuTime() { return gpuTime; } + + double gpuTime{ 0.0 }; + +signals: + void dirty(); +}; + +class LinearDepthPass { +public: + using Inputs = render::VaryingSet2; + using Outputs = render::VaryingSet5; + using Config = LinearDepthPassConfig; + using JobModel = render::Job::ModelIO; + + LinearDepthPass(); + + void configure(const Config& config); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + +private: + typedef gpu::BufferView UniformBufferView; + + LinearDepthFramebufferPointer _linearDepthFramebuffer; + + const gpu::PipelinePointer& getLinearDepthPipeline(); + gpu::PipelinePointer _linearDepthPipeline; + + const gpu::PipelinePointer& getDownsamplePipeline(); + gpu::PipelinePointer _downsamplePipeline; + + gpu::RangeTimer _gpuTimer; +}; + + +// SurfaceGeometryFramebuffer is a helper class gathering in one place theframebuffers and targets describing the surface geometry linear depth and curvature generated +// from a z buffer and a normal buffer +class SurfaceGeometryFramebuffer { +public: + SurfaceGeometryFramebuffer(); + + gpu::FramebufferPointer getCurvatureFramebuffer(); + gpu::TexturePointer getCurvatureTexture(); + + gpu::FramebufferPointer getLowCurvatureFramebuffer(); + gpu::TexturePointer getLowCurvatureTexture(); + + gpu::FramebufferPointer getBlurringFramebuffer(); + gpu::TexturePointer getBlurringTexture(); + + // Update the source framebuffer size which will drive the allocation of all the other resources. + void updateLinearDepth(const gpu::TexturePointer& linearDepthBuffer); + gpu::TexturePointer getLinearDepthTexture(); + const glm::ivec2& getSourceFrameSize() const { return _frameSize; } + + void setResolutionLevel(int level); + int getResolutionLevel() const { return _resolutionLevel; } + +protected: + void clear(); + void allocate(); + + gpu::TexturePointer _linearDepthTexture; + + gpu::FramebufferPointer _curvatureFramebuffer; + gpu::TexturePointer _curvatureTexture; + + gpu::FramebufferPointer _blurringFramebuffer; + gpu::TexturePointer _blurringTexture; + + gpu::FramebufferPointer _lowCurvatureFramebuffer; + gpu::TexturePointer _lowCurvatureTexture; + + glm::ivec2 _frameSize; + int _resolutionLevel{ 0 }; +}; + +using SurfaceGeometryFramebufferPointer = std::shared_ptr; + +class SurfaceGeometryPassConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float depthThreshold MEMBER depthThreshold NOTIFY dirty) + Q_PROPERTY(float basisScale MEMBER basisScale NOTIFY dirty) + Q_PROPERTY(float curvatureScale MEMBER curvatureScale NOTIFY dirty) + Q_PROPERTY(int resolutionLevel MEMBER resolutionLevel NOTIFY dirty) + + Q_PROPERTY(float diffuseFilterScale MEMBER diffuseFilterScale NOTIFY dirty) + Q_PROPERTY(float diffuseDepthThreshold MEMBER diffuseDepthThreshold NOTIFY dirty) + + Q_PROPERTY(double gpuTime READ getGpuTime) +public: + SurfaceGeometryPassConfig() : render::Job::Config(true) {} + + float depthThreshold{ 5.0f }; // centimeters + float basisScale{ 1.0f }; + float curvatureScale{ 10.0f }; + int resolutionLevel{ 1 }; + float diffuseFilterScale{ 0.2f }; + float diffuseDepthThreshold{ 1.0f }; + + double getGpuTime() { return gpuTime; } + + double gpuTime{ 0.0 }; + +signals: + void dirty(); +}; + +class SurfaceGeometryPass { +public: + using Inputs = render::VaryingSet3; + using Outputs = render::VaryingSet4; + using Config = SurfaceGeometryPassConfig; + using JobModel = render::Job::ModelIO; + + SurfaceGeometryPass(); + + void configure(const Config& config); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + + + float getCurvatureDepthThreshold() const { return _parametersBuffer.get().curvatureInfo.x; } + float getCurvatureBasisScale() const { return _parametersBuffer.get().curvatureInfo.y; } + float getCurvatureScale() const { return _parametersBuffer.get().curvatureInfo.w; } + int getResolutionLevel() const { return (int)_parametersBuffer.get().resolutionInfo.w; } + +private: + typedef gpu::BufferView UniformBufferView; + + // Class describing the uniform buffer with all the parameters common to the AO shaders + class Parameters { + public: + // Resolution info + glm::vec4 resolutionInfo { 0.0f, 0.0f, 0.0f, 1.0f }; // Default Curvature & Diffusion is running half res + // Curvature algorithm + glm::vec4 curvatureInfo{ 0.0f }; + + Parameters() {} + }; + gpu::BufferView _parametersBuffer; + + + SurfaceGeometryFramebufferPointer _surfaceGeometryFramebuffer; + + const gpu::PipelinePointer& getCurvaturePipeline(); + + gpu::PipelinePointer _curvaturePipeline; + + render::BlurGaussianDepthAware _diffusePass; + + + gpu::RangeTimer _gpuTimer; +}; + +#endif // hifi_SurfaceGeometryPass_h diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index b28f271f9d..b0ac40a839 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -18,6 +18,8 @@ #include "FramebufferCache.h" +#include "toneMapping_frag.h" + const int ToneMappingEffect_ParamsSlot = 0; const int ToneMappingEffect_LightingMapSlot = 0; @@ -27,67 +29,7 @@ ToneMappingEffect::ToneMappingEffect() { } void ToneMappingEffect::init() { - const char BlitTextureGamma_frag[] = R"SCRIBE( - // Generated on Sat Oct 24 09:34:37 2015 - // - // Draw texture 0 fetched at texcoord.xy - // - // Created by Sam Gateau on 6/22/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 - // - - struct ToneMappingParams { - vec4 _exp_2powExp_s0_s1; - ivec4 _toneCurve_s0_s1_s2; - }; - - const float INV_GAMMA_22 = 1.0 / 2.2; - const int ToneCurveNone = 0; - const int ToneCurveGamma22 = 1; - const int ToneCurveReinhard = 2; - const int ToneCurveFilmic = 3; - - uniform toneMappingParamsBuffer { - ToneMappingParams params; - }; - float getTwoPowExposure() { - return params._exp_2powExp_s0_s1.y; - } - int getToneCurve() { - return params._toneCurve_s0_s1_s2.x; - } - - uniform sampler2D colorMap; - - in vec2 varTexCoord0; - out vec4 outFragColor; - - void main(void) { - vec4 fragColorRaw = texture(colorMap, varTexCoord0); - vec3 fragColor = fragColorRaw.xyz; - - vec3 srcColor = fragColor * getTwoPowExposure(); - - int toneCurve = getToneCurve(); - vec3 tonedColor = srcColor; - if (toneCurve == ToneCurveFilmic) { - vec3 x = max(vec3(0.0), srcColor-0.004); - tonedColor = (x * (6.2 * x + 0.5)) / (x * (6.2 * x + 1.7) + 0.06); - } else if (toneCurve == ToneCurveReinhard) { - tonedColor = srcColor/(1.0 + srcColor); - tonedColor = pow(tonedColor, vec3(INV_GAMMA_22)); - } else if (toneCurve == ToneCurveGamma22) { - tonedColor = pow(srcColor, vec3(INV_GAMMA_22)); - } // else None toned = src - - outFragColor = vec4(tonedColor, 1.0); - } - - )SCRIBE"; - auto blitPS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(BlitTextureGamma_frag))); + auto blitPS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(toneMapping_frag))); auto blitVS = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto blitProgram = gpu::ShaderPointer(gpu::Shader::createProgram(blitVS, blitPS)); @@ -102,26 +44,28 @@ void ToneMappingEffect::init() { } void ToneMappingEffect::setExposure(float exposure) { - _parametersBuffer.edit()._exposure = exposure; - _parametersBuffer.edit()._twoPowExposure = pow(2.0, exposure); + auto& params = _parametersBuffer.get(); + if (params._exposure != exposure) { + _parametersBuffer.edit()._exposure = exposure; + _parametersBuffer.edit()._twoPowExposure = pow(2.0, exposure); + } } void ToneMappingEffect::setToneCurve(ToneCurve curve) { - _parametersBuffer.edit()._toneCurve = curve; + auto& params = _parametersBuffer.get(); + if (params._toneCurve != curve) { + _parametersBuffer.edit()._toneCurve = curve; + } } -void ToneMappingEffect::render(RenderArgs* args) { +void ToneMappingEffect::render(RenderArgs* args, const gpu::TexturePointer& lightingBuffer, const gpu::FramebufferPointer& destinationFramebuffer) { if (!_blitLightBuffer) { init(); } - auto framebufferCache = DependencyManager::get(); + auto framebufferSize = glm::ivec2(lightingBuffer->getDimensions()); gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); - QSize framebufferSize = framebufferCache->getFrameBufferSize(); - - auto lightingBuffer = framebufferCache->getLightingTexture(); - auto destFbo = framebufferCache->getPrimaryFramebuffer(); - batch.setFramebuffer(destFbo); + batch.setFramebuffer(destinationFramebuffer); // FIXME: Generate the Luminosity map //batch.generateTextureMips(lightingBuffer); @@ -129,17 +73,8 @@ void ToneMappingEffect::render(RenderArgs* args) { batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(glm::mat4()); batch.setViewTransform(Transform()); - { - float sMin = args->_viewport.x / (float)framebufferSize.width(); - float sWidth = args->_viewport.z / (float)framebufferSize.width(); - float tMin = args->_viewport.y / (float)framebufferSize.height(); - float tHeight = args->_viewport.w / (float)framebufferSize.height(); - Transform model; - batch.setPipeline(_blitLightBuffer); - model.setTranslation(glm::vec3(sMin, tMin, 0.0)); - model.setScale(glm::vec3(sWidth, tHeight, 1.0)); - batch.setModelTransform(model); - } + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport)); + batch.setPipeline(_blitLightBuffer); batch.setUniformBuffer(ToneMappingEffect_ParamsSlot, _parametersBuffer); batch.setResourceTexture(ToneMappingEffect_LightingMapSlot, lightingBuffer); @@ -149,15 +84,13 @@ void ToneMappingEffect::render(RenderArgs* args) { void ToneMappingDeferred::configure(const Config& config) { - if (config.exposure >= 0.0f) { - _toneMappingEffect.setExposure(config.exposure); - } - - if (config.curve >= 0) { - _toneMappingEffect.setToneCurve((ToneMappingEffect::ToneCurve)config.curve); - } + _toneMappingEffect.setExposure(config.exposure); + _toneMappingEffect.setToneCurve((ToneMappingEffect::ToneCurve)config.curve); } -void ToneMappingDeferred::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { - _toneMappingEffect.render(renderContext->args); +void ToneMappingDeferred::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs) { + + auto lightingBuffer = inputs.get0()->getRenderBuffer(0); + auto destFbo = inputs.get1(); + _toneMappingEffect.render(renderContext->args, lightingBuffer, destFbo); } diff --git a/libraries/render-utils/src/ToneMappingEffect.h b/libraries/render-utils/src/ToneMappingEffect.h index 3dd212c2dc..9e8b3f6aa4 100644 --- a/libraries/render-utils/src/ToneMappingEffect.h +++ b/libraries/render-utils/src/ToneMappingEffect.h @@ -27,7 +27,7 @@ public: ToneMappingEffect(); virtual ~ToneMappingEffect() {} - void render(RenderArgs* args); + void render(RenderArgs* args, const gpu::TexturePointer& lightingBuffer, const gpu::FramebufferPointer& destinationBuffer); void setExposure(float exposure); float getExposure() const { return _parametersBuffer.get()._exposure; } @@ -71,7 +71,7 @@ class ToneMappingConfig : public render::Job::Config { public: ToneMappingConfig() : render::Job::Config(true) {} - void setExposure(float newExposure) { exposure = std::max(0.0f, newExposure); emit dirty(); } + void setExposure(float newExposure) { exposure = newExposure; emit dirty(); } void setCurve(int newCurve) { curve = std::max((int)ToneMappingEffect::None, std::min((int)ToneMappingEffect::Filmic, newCurve)); emit dirty(); } @@ -83,11 +83,13 @@ signals: class ToneMappingDeferred { public: + // Inputs: lightingFramebuffer, destinationFramebuffer + using Inputs = render::VaryingSet2; using Config = ToneMappingConfig; - using JobModel = render::Job::Model; + using JobModel = render::Job::ModelI; void configure(const Config& config); - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs); ToneMappingEffect _toneMappingEffect; }; diff --git a/libraries/render-utils/src/debug_deferred_buffer.slf b/libraries/render-utils/src/debug_deferred_buffer.slf index b323836657..079f79c06e 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slf +++ b/libraries/render-utils/src/debug_deferred_buffer.slf @@ -14,9 +14,14 @@ <@include DeferredBufferRead.slh@> -uniform sampler2D pyramidMap; +uniform sampler2D linearDepthMap; +uniform sampler2D halfLinearDepthMap; +uniform sampler2D halfNormalMap; uniform sampler2D occlusionMap; uniform sampler2D occlusionBlurredMap; +uniform sampler2D curvatureMap; +uniform sampler2D diffusedCurvatureMap; +uniform sampler2D scatteringMap; in vec2 uv; out vec4 outFragColor; diff --git a/libraries/render-utils/src/deferred_light.slv b/libraries/render-utils/src/deferred_light.slv index d0e3a754d3..27845bf6a2 100644 --- a/libraries/render-utils/src/deferred_light.slv +++ b/libraries/render-utils/src/deferred_light.slv @@ -5,18 +5,31 @@ // deferred_light.vert // vertex shader // -// Created by Andrzej Kapolka on 9/18/14. +// Created by Sam Gateau on 6/16/16. // 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 gpu/Inputs.slh@> - out vec2 _texCoord0; +uniform vec4 texcoordFrameTransform; + void main(void) { - _texCoord0 = inTexCoord0.st; - gl_Position = inPosition; + const float depth = 1.0; + const vec4 UNIT_QUAD[4] = vec4[4]( + vec4(-1.0, -1.0, depth, 1.0), + vec4(1.0, -1.0, depth, 1.0), + vec4(-1.0, 1.0, depth, 1.0), + vec4(1.0, 1.0, depth, 1.0) + ); + vec4 pos = UNIT_QUAD[gl_VertexID]; + + _texCoord0 = (pos.xy + 1) * 0.5; + + _texCoord0 *= texcoordFrameTransform.zw; + _texCoord0 += texcoordFrameTransform.xy; + + gl_Position = pos; } diff --git a/libraries/render-utils/src/deferred_light_limited.slv b/libraries/render-utils/src/deferred_light_limited.slv index e9f3e1bdb3..81ec882bdf 100644 --- a/libraries/render-utils/src/deferred_light_limited.slv +++ b/libraries/render-utils/src/deferred_light_limited.slv @@ -5,7 +5,7 @@ // deferred_light_limited.vert // vertex shader // -// Created by Andrzej Kapolka on 9/19/14. +// Created by Sam Gateau on 6/16/16. // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -18,17 +18,40 @@ <$declareStandardTransform()$> -uniform mat4 texcoordMat; +uniform vec4 sphereParam; out vec4 _texCoord0; void main(void) { - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>; + if (sphereParam.w != 0.0) { - vec4 projected = gl_Position / gl_Position.w; - _texCoord0 = vec4(dot(projected, texcoordMat[0]) * gl_Position.w, - dot(projected, texcoordMat[1]) * gl_Position.w, 0.0, gl_Position.w); + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>; + + vec4 projected = gl_Position / gl_Position.w; + projected.xy = (projected.xy + 1.0) * 0.5; + + if (cam_isStereo()) { + projected.x = 0.5 * (projected.x + cam_getStereoSide()); + } + _texCoord0 = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; + } else { + const float depth = -1.0; //Draw at near plane + const vec4 UNIT_QUAD[4] = vec4[4]( + vec4(-1.0, -1.0, depth, 1.0), + vec4(1.0, -1.0, depth, 1.0), + vec4(-1.0, 1.0, depth, 1.0), + vec4(1.0, 1.0, depth, 1.0) + ); + vec4 pos = UNIT_QUAD[gl_VertexID]; + + _texCoord0 = vec4((pos.xy + 1) * 0.5, 0.0, 1.0); + + if (cam_isStereo()) { + _texCoord0.x = 0.5 * (_texCoord0.x + cam_getStereoSide()); + } + gl_Position = pos; + } } diff --git a/libraries/render-utils/src/deferred_light_spot.slv b/libraries/render-utils/src/deferred_light_spot.slv index 8365899fde..6ae133b7a5 100755 --- a/libraries/render-utils/src/deferred_light_spot.slv +++ b/libraries/render-utils/src/deferred_light_spot.slv @@ -18,7 +18,6 @@ <$declareStandardTransform()$> -uniform mat4 texcoordMat; uniform vec4 coneParam; out vec4 _texCoord0; @@ -38,14 +37,33 @@ void main(void) { } else { coneVertex.z = 0.0; } + + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, coneVertex, gl_Position)$>; + vec4 projected = gl_Position / gl_Position.w; + projected.xy = (projected.xy + 1.0) * 0.5; + + if (cam_isStereo()) { + projected.x = 0.5 * (projected.x + cam_getStereoSide()); + } + _texCoord0 = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; + } else { + const float depth = -1.0; //Draw at near plane + const vec4 UNIT_QUAD[4] = vec4[4]( + vec4(-1.0, -1.0, depth, 1.0), + vec4(1.0, -1.0, depth, 1.0), + vec4(-1.0, 1.0, depth, 1.0), + vec4(1.0, 1.0, depth, 1.0) + ); + vec4 pos = UNIT_QUAD[gl_VertexID]; + + _texCoord0 = vec4((pos.xy + 1) * 0.5, 0.0, 1.0); + if (cam_isStereo()) { + _texCoord0.x = 0.5 * (_texCoord0.x + cam_getStereoSide()); + } + gl_Position = pos; } - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, coneVertex, gl_Position)$>; - - vec4 projected = gl_Position / gl_Position.w; - _texCoord0 = vec4(dot(projected, texcoordMat[0]) * gl_Position.w, - dot(projected, texcoordMat[1]) * gl_Position.w, 0.0, gl_Position.w); } diff --git a/libraries/render-utils/src/directional_ambient_light.slf b/libraries/render-utils/src/directional_ambient_light.slf index 2ee818fdba..5b591cb01f 100755 --- a/libraries/render-utils/src/directional_ambient_light.slf +++ b/libraries/render-utils/src/directional_ambient_light.slf @@ -12,43 +12,50 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + + <@include DeferredBufferRead.slh@> <@include DeferredGlobalLight.slh@> <$declareEvalLightmappedColor()$> -<$declareEvalAmbientSphereGlobalColor()$> + +<$declareEvalAmbientSphereGlobalColor(supportScattering)$> + in vec2 _texCoord0; out vec4 _fragColor; void main(void) { - DeferredTransform deferredTransform = getDeferredTransform(); + DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); float shadowAttenuation = 1.0; if (frag.mode == FRAG_MODE_UNLIT) { - _fragColor = vec4(frag.diffuse, 1.0); + discard; } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) { - vec3 color = evalLightmappedColor( - deferredTransform.viewInverse, - shadowAttenuation, - frag.obscurance, - frag.normal, - frag.diffuse, - frag.specularVal.xyz); - _fragColor = vec4(color, 1.0); + discard; } else { - vec3 color = evalAmbientSphereGlobalColor( - deferredTransform.viewInverse, - shadowAttenuation, - frag.obscurance, - frag.position.xyz, - frag.normal, - frag.diffuse, - frag.metallic, - frag.emissive, - frag.roughness); - _fragColor = vec4(color, frag.normalVal.a); + vec4 midNormalCurvature; + vec4 lowNormalCurvature; + if (frag.mode == FRAG_MODE_SCATTERING) { + unpackMidLowNormalCurvature(_texCoord0, midNormalCurvature, lowNormalCurvature); + } + + vec3 color = evalAmbientSphereGlobalColor( + getViewInverse(), + shadowAttenuation, + frag.obscurance, + frag.position.xyz, + frag.normal, + frag.albedo, + frag.fresnel, + frag.metallic, + frag.roughness, + frag.scattering, + midNormalCurvature, + lowNormalCurvature); + _fragColor = vec4(color, 1.0); + } } diff --git a/libraries/render-utils/src/directional_ambient_light_shadow.slf b/libraries/render-utils/src/directional_ambient_light_shadow.slf index 20ceea9379..d3778c9228 100644 --- a/libraries/render-utils/src/directional_ambient_light_shadow.slf +++ b/libraries/render-utils/src/directional_ambient_light_shadow.slf @@ -17,40 +17,42 @@ <@include DeferredGlobalLight.slh@> <$declareEvalLightmappedColor()$> -<$declareEvalAmbientSphereGlobalColor()$> +<$declareEvalAmbientSphereGlobalColor(isScattering)$> in vec2 _texCoord0; out vec4 _fragColor; void main(void) { - DeferredTransform deferredTransform = getDeferredTransform(); + DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); - vec4 worldPos = deferredTransform.viewInverse * vec4(frag.position.xyz, 1.0); + vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0); float shadowAttenuation = evalShadowAttenuation(worldPos); if (frag.mode == FRAG_MODE_UNLIT) { - _fragColor = vec4(frag.diffuse, 1.0); + discard; } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) { - vec3 color = evalLightmappedColor( - deferredTransform.viewInverse, - shadowAttenuation, - frag.obscurance, - frag.normal, - frag.diffuse, - frag.specularVal.xyz); - _fragColor = vec4(color, 1.0); + discard; } else { + vec4 midNormalCurvature; + vec4 lowNormalCurvature; + if (frag.mode == FRAG_MODE_SCATTERING) { + unpackMidLowNormalCurvature(_texCoord0, midNormalCurvature, lowNormalCurvature); + } vec3 color = evalAmbientSphereGlobalColor( - deferredTransform.viewInverse, + getViewInverse(), shadowAttenuation, frag.obscurance, frag.position.xyz, frag.normal, - frag.diffuse, + frag.albedo, + frag.fresnel, frag.metallic, - frag.emissive, - frag.roughness); - _fragColor = vec4(color, frag.normalVal.a); + frag.roughness, + frag.scattering, + midNormalCurvature, + lowNormalCurvature); + + _fragColor = vec4(color, 1.0); } } diff --git a/libraries/render-utils/src/directional_light.slf b/libraries/render-utils/src/directional_light.slf index ef61e9a030..cda03b0779 100644 --- a/libraries/render-utils/src/directional_light.slf +++ b/libraries/render-utils/src/directional_light.slf @@ -22,34 +22,26 @@ in vec2 _texCoord0; out vec4 _fragColor; void main(void) { - DeferredTransform deferredTransform = getDeferredTransform(); + DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); float shadowAttenuation = 1.0; - // Light mapped or not ? if (frag.mode == FRAG_MODE_UNLIT) { - _fragColor = vec4(frag.diffuse, 1.0); + discard; } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) { - vec3 color = evalLightmappedColor( - deferredTransform.viewInverse, - shadowAttenuation, - frag.obscurance, - frag.normal, - frag.diffuse, - frag.specularVal.xyz); - _fragColor = vec4(color, 1.0); + discard; } else { vec3 color = evalAmbientGlobalColor( - deferredTransform.viewInverse, + getViewInverse(), shadowAttenuation, frag.obscurance, frag.position.xyz, frag.normal, - frag.diffuse, + frag.albedo, + frag.fresnel, frag.metallic, - frag.emissive, frag.roughness); - _fragColor = vec4(color, frag.normalVal.a); + _fragColor = vec4(color, 1.0); } } diff --git a/libraries/render-utils/src/directional_light_shadow.slf b/libraries/render-utils/src/directional_light_shadow.slf index 5b09d47e80..7f98330f84 100644 --- a/libraries/render-utils/src/directional_light_shadow.slf +++ b/libraries/render-utils/src/directional_light_shadow.slf @@ -23,35 +23,27 @@ in vec2 _texCoord0; out vec4 _fragColor; void main(void) { - DeferredTransform deferredTransform = getDeferredTransform(); + DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); - vec4 worldPos = deferredTransform.viewInverse * vec4(frag.position.xyz, 1.0); + vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0); float shadowAttenuation = evalShadowAttenuation(worldPos); - // Light mapped or not ? if (frag.mode == FRAG_MODE_UNLIT) { - _fragColor = vec4(frag.diffuse, 1.0); + discard; } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) { - vec3 color = evalLightmappedColor( - deferredTransform.viewInverse, - shadowAttenuation, - frag.obscurance, - frag.normal, - frag.diffuse, - frag.specularVal.xyz); - _fragColor = vec4(color, 1.0); + discard; } else { vec3 color = evalAmbientGlobalColor( - deferredTransform.viewInverse, + getViewInverse(), shadowAttenuation, frag.obscurance, frag.position.xyz, frag.normal, - frag.diffuse, + frag.albedo, + frag.fresnel, frag.metallic, - frag.emissive, frag.roughness); - _fragColor = vec4(color, frag.normalVal.a); + _fragColor = vec4(color, 1.0); } } diff --git a/libraries/render-utils/src/directional_skybox_light.slf b/libraries/render-utils/src/directional_skybox_light.slf index f0c7bb476f..6130c8ac8e 100755 --- a/libraries/render-utils/src/directional_skybox_light.slf +++ b/libraries/render-utils/src/directional_skybox_light.slf @@ -16,41 +16,42 @@ <@include DeferredGlobalLight.slh@> <$declareEvalLightmappedColor()$> -<$declareEvalSkyboxGlobalColor()$> +<$declareEvalSkyboxGlobalColor(isScattering)$> in vec2 _texCoord0; out vec4 _fragColor; void main(void) { - DeferredTransform deferredTransform = getDeferredTransform(); + DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); float shadowAttenuation = 1.0; // Light mapped or not ? if (frag.mode == FRAG_MODE_UNLIT) { - _fragColor = vec4(frag.diffuse, 1.0); + discard; } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) { - vec3 color = evalLightmappedColor( - deferredTransform.viewInverse, - shadowAttenuation, - frag.obscurance, - frag.normal, - frag.diffuse, - frag.specularVal.xyz); - _fragColor = vec4(color, 1.0); + discard; } else { - vec3 color = evalSkyboxGlobalColor( - deferredTransform.viewInverse, + vec4 midNormalCurvature; + vec4 lowNormalCurvature; + if (frag.mode == FRAG_MODE_SCATTERING) { + unpackMidLowNormalCurvature(_texCoord0, midNormalCurvature, lowNormalCurvature); + } + vec3 color = evalSkyboxGlobalColor( + getViewInverse(), shadowAttenuation, frag.obscurance, frag.position.xyz, frag.normal, - frag.diffuse, + frag.albedo, + frag.fresnel, frag.metallic, - frag.emissive, - frag.roughness); + frag.roughness, + frag.scattering, + midNormalCurvature, + lowNormalCurvature); + _fragColor = vec4(color, 1.0); - _fragColor = vec4(color, frag.normalVal.a); } } diff --git a/libraries/render-utils/src/directional_skybox_light_shadow.slf b/libraries/render-utils/src/directional_skybox_light_shadow.slf index 6a233e5985..3ca0f71df5 100644 --- a/libraries/render-utils/src/directional_skybox_light_shadow.slf +++ b/libraries/render-utils/src/directional_skybox_light_shadow.slf @@ -17,42 +17,44 @@ <@include DeferredGlobalLight.slh@> <$declareEvalLightmappedColor()$> -<$declareEvalSkyboxGlobalColor()$> +<$declareEvalSkyboxGlobalColor(isScattering)$> in vec2 _texCoord0; out vec4 _fragColor; void main(void) { - DeferredTransform deferredTransform = getDeferredTransform(); + DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); - vec4 worldPos = deferredTransform.viewInverse * vec4(frag.position.xyz, 1.0); + vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0); float shadowAttenuation = evalShadowAttenuation(worldPos); // Light mapped or not ? if (frag.mode == FRAG_MODE_UNLIT) { - _fragColor = vec4(frag.diffuse, 1.0); + discard; } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) { - vec3 color = evalLightmappedColor( - deferredTransform.viewInverse, - shadowAttenuation, - frag.obscurance, - frag.normal, - frag.diffuse, - frag.specularVal.xyz); - _fragColor = vec4(color, 1.0); + discard; } else { + vec4 midNormalCurvature; + vec4 lowNormalCurvature; + if (frag.mode == FRAG_MODE_SCATTERING) { + unpackMidLowNormalCurvature(_texCoord0, midNormalCurvature, lowNormalCurvature); + } vec3 color = evalSkyboxGlobalColor( - deferredTransform.viewInverse, + getViewInverse(), shadowAttenuation, frag.obscurance, frag.position.xyz, frag.normal, - frag.diffuse, + frag.albedo, + frag.fresnel, frag.metallic, - frag.emissive, - frag.roughness); + frag.roughness, + frag.scattering, + midNormalCurvature, + lowNormalCurvature); - _fragColor = vec4(color, frag.normalVal.a); + + _fragColor = vec4(color, 1.0); } } diff --git a/libraries/render-utils/src/glowLine.slf b/libraries/render-utils/src/glowLine.slf new file mode 100644 index 0000000000..edebc99c81 --- /dev/null +++ b/libraries/render-utils/src/glowLine.slf @@ -0,0 +1,35 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Bradley Austin Davis on 2016/07/05 +// Copyright 2013-2016 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 +// + +layout(location = 0) in vec4 inColor; +layout(location = 1) in vec3 inLineDistance; + +out vec4 _fragColor; + +void main(void) { + vec2 d = inLineDistance.xy; + d.y = abs(d.y); + d.x = abs(d.x); + if (d.x > 1.0) { + d.x = (d.x - 1.0) / 0.02; + } else { + d.x = 0.0; + } + float alpha = 1.0 - length(d); + if (alpha <= 0.0) { + discard; + } + alpha = pow(alpha, 10.0); + if (alpha < 0.05) { + discard; + } + _fragColor = vec4(inColor.rgb, alpha); +} diff --git a/libraries/render-utils/src/glowLine.slg b/libraries/render-utils/src/glowLine.slg new file mode 100644 index 0000000000..9af8eaa4d0 --- /dev/null +++ b/libraries/render-utils/src/glowLine.slg @@ -0,0 +1,102 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Bradley Austin Davis on 2016/07/05 +// Copyright 2013-2016 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 +// +#extension GL_EXT_geometry_shader4 : enable + +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + +layout(location = 0) in vec4 inColor[]; + +layout(location = 0) out vec4 outColor; +layout(location = 1) out vec3 outLineDistance; + +layout(lines) in; +layout(triangle_strip, max_vertices = 24) out; + +vec3 ndcToEyeSpace(in vec4 v) { + TransformCamera cam = getTransformCamera(); + vec4 u = cam._projectionInverse * v; + return u.xyz / u.w; +} + +vec2 toScreenSpace(in vec4 v) +{ + TransformCamera cam = getTransformCamera(); + vec4 u = cam._projection * cam._view * v; + return u.xy / u.w; +} + +vec3[2] getOrthogonals(in vec3 n, float scale) { + float yDot = abs(dot(n, vec3(0, 1, 0))); + + vec3 result[2]; + if (yDot < 0.9) { + result[0] = normalize(cross(n, vec3(0, 1, 0))); + } else { + result[0] = normalize(cross(n, vec3(1, 0, 0))); + } + // The cross of result[0] and n is orthogonal to both, which are orthogonal to each other + result[1] = cross(result[0], n); + result[0] *= scale; + result[1] *= scale; + return result; +} + + +vec2 orthogonal(vec2 v) { + vec2 result = v.yx; + result.y *= -1.0; + return result; +} + +void main() { + vec2 endpoints[2]; + vec3 eyeSpace[2]; + TransformCamera cam = getTransformCamera(); + for (int i = 0; i < 2; ++i) { + eyeSpace[i] = ndcToEyeSpace(gl_PositionIn[i]); + endpoints[i] = gl_PositionIn[i].xy / gl_PositionIn[i].w; + } + vec2 lineNormal = normalize(endpoints[1] - endpoints[0]); + vec2 lineOrthogonal = orthogonal(lineNormal); + lineNormal *= 0.02; + lineOrthogonal *= 0.02; + + gl_Position = gl_PositionIn[0]; + gl_Position.xy -= lineOrthogonal; + outColor = inColor[0]; + outLineDistance = vec3(-1.02, -1, gl_Position.z); + EmitVertex(); + + gl_Position = gl_PositionIn[0]; + gl_Position.xy += lineOrthogonal; + outColor = inColor[0]; + outLineDistance = vec3(-1.02, 1, gl_Position.z); + EmitVertex(); + + gl_Position = gl_PositionIn[1]; + gl_Position.xy -= lineOrthogonal; + outColor = inColor[1]; + outLineDistance = vec3(1.02, -1, gl_Position.z); + EmitVertex(); + + gl_Position = gl_PositionIn[1]; + gl_Position.xy += lineOrthogonal; + outColor = inColor[1]; + outLineDistance = vec3(1.02, 1, gl_Position.z); + EmitVertex(); + + EndPrimitive(); +} + + + + diff --git a/tests/gpu-test/src/unlit.slv b/libraries/render-utils/src/glowLine.slv similarity index 59% rename from tests/gpu-test/src/unlit.slv rename to libraries/render-utils/src/glowLine.slv index d51d817429..aa126fe31a 100644 --- a/tests/gpu-test/src/unlit.slv +++ b/libraries/render-utils/src/glowLine.slv @@ -2,35 +2,25 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// simple.vert -// vertex shader -// -// Created by Andrzej Kapolka on 9/15/14. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2016/07/05 +// Copyright 2013-2016 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 gpu/Inputs.slh@> - +<@include gpu/Color.slh@> <@include gpu/Transform.slh@> - <$declareStandardTransform()$> -// the interpolated normal -out vec3 _normal; -out vec3 _color; -out vec2 _texCoord0; +layout(location = 0) out vec4 _color; void main(void) { - _color = inColor.rgb; - _texCoord0 = inTexCoord0.st; + _color = inColor; // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> - _normal = vec3(0.0, 0.0, 1.0); } \ No newline at end of file diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf index f1dcc942c9..daeead65ec 100755 --- a/libraries/render-utils/src/model.slf +++ b/libraries/render-utils/src/model.slf @@ -22,12 +22,14 @@ in vec4 _position; in vec3 _normal; in vec3 _color; in vec2 _texCoord0; +in vec2 _texCoord1; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); - <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$> + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> float opacity = 1.0; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; @@ -43,6 +45,8 @@ void main(void) { vec3 emissive = getMaterialEmissive(mat); <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; + float scattering = getMaterialScattering(mat); + packDeferredFragment( normalize(_normal.xyz), opacity, @@ -50,5 +54,6 @@ void main(void) { roughness, getMaterialMetallic(mat), emissive, - occlusionTex); + occlusionTex, + scattering); } diff --git a/libraries/render-utils/src/model.slv b/libraries/render-utils/src/model.slv index f1989dcf76..06f6030e77 100755 --- a/libraries/render-utils/src/model.slv +++ b/libraries/render-utils/src/model.slv @@ -22,6 +22,7 @@ out vec3 _color; out float _alpha; out vec2 _texCoord0; +out vec2 _texCoord1; out vec4 _position; out vec3 _normal; @@ -31,10 +32,11 @@ void main(void) { TexMapArray texMapArray = getTexMapArray(); <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> } diff --git a/libraries/render-utils/src/model_lightmap.slf b/libraries/render-utils/src/model_lightmap.slf index 3afbbfd405..3a8cfde290 100755 --- a/libraries/render-utils/src/model_lightmap.slf +++ b/libraries/render-utils/src/model_lightmap.slf @@ -29,8 +29,8 @@ in vec3 _color; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); - <$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness)$> - <$fetchMaterialLightmap(_texCoord1, lightmapVal)$> + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$> packDeferredFragmentLightmap( diff --git a/libraries/render-utils/src/model_lightmap.slv b/libraries/render-utils/src/model_lightmap.slv index f73b6a02a7..161ceed14c 100755 --- a/libraries/render-utils/src/model_lightmap.slv +++ b/libraries/render-utils/src/model_lightmap.slv @@ -39,6 +39,6 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> } diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slf b/libraries/render-utils/src/model_lightmap_normal_map.slf index 9ccc6e5352..64c61e255d 100755 --- a/libraries/render-utils/src/model_lightmap_normal_map.slf +++ b/libraries/render-utils/src/model_lightmap_normal_map.slf @@ -30,8 +30,8 @@ in vec3 _color; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); - <$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness, normalTexel)$> - <$fetchMaterialLightmap(_texCoord1, lightmapVal)$> + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness, normalTexel)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$> vec3 viewNormal; <$tangentToViewSpace(normalTexel, _normal, _tangent, viewNormal)$> diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slv b/libraries/render-utils/src/model_lightmap_normal_map.slv index cb333f50e3..5fb60d9227 100755 --- a/libraries/render-utils/src/model_lightmap_normal_map.slv +++ b/libraries/render-utils/src/model_lightmap_normal_map.slv @@ -39,6 +39,6 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> - <$transformModelToEyeDir(cam, obj, inTangent.xyz, _tangent)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToWorldDir(cam, obj, inTangent.xyz, _tangent)$> } diff --git a/libraries/render-utils/src/model_lightmap_normal_specular_map.slf b/libraries/render-utils/src/model_lightmap_normal_specular_map.slf index 71909a789f..34a116eac1 100755 --- a/libraries/render-utils/src/model_lightmap_normal_specular_map.slf +++ b/libraries/render-utils/src/model_lightmap_normal_specular_map.slf @@ -30,8 +30,8 @@ in vec3 _color; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); - <$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness, normalTexel, metallicTex)$> - <$fetchMaterialLightmap(_texCoord1, lightmapVal)$> + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness, normalTexel, metallicTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$> vec3 viewNormal; <$tangentToViewSpace(normalTexel, _normal, _tangent, viewNormal)$> diff --git a/libraries/render-utils/src/model_lightmap_specular_map.slf b/libraries/render-utils/src/model_lightmap_specular_map.slf index 5eefefdc29..4dbc10a834 100755 --- a/libraries/render-utils/src/model_lightmap_specular_map.slf +++ b/libraries/render-utils/src/model_lightmap_specular_map.slf @@ -29,8 +29,8 @@ in vec3 _color; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); - <$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness, _SCRIBE_NULL, metallicTex)$> - <$fetchMaterialLightmap(_texCoord1, lightmapVal)$> + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness, _SCRIBE_NULL, metallicTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$> packDeferredFragmentLightmap( normalize(_normal), diff --git a/libraries/render-utils/src/model_normal_map.slf b/libraries/render-utils/src/model_normal_map.slf index 519b41e17f..3acdedab2a 100755 --- a/libraries/render-utils/src/model_normal_map.slf +++ b/libraries/render-utils/src/model_normal_map.slf @@ -17,10 +17,11 @@ <@include model/Material.slh@> <@include MaterialTextures.slh@> -<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, _SCRIBE_NULL, EMISSIVE, OCCLUSION, SCATTERING)$> in vec4 _position; in vec2 _texCoord0; +in vec2 _texCoord1; in vec3 _normal; in vec3 _tangent; in vec3 _color; @@ -28,7 +29,8 @@ in vec3 _color; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); - <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex, occlusionTex)$> + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex, scatteringTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> float opacity = 1.0; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; @@ -47,6 +49,9 @@ void main(void) { vec3 viewNormal; <$tangentToViewSpace(normalTex, _normal, _tangent, viewNormal)$> + float scattering = getMaterialScattering(mat); + <$evalMaterialScattering(scatteringTex, scattering, matKey, scattering)$>; + packDeferredFragment( viewNormal, opacity, @@ -54,5 +59,6 @@ void main(void) { roughness, getMaterialMetallic(mat), emissive, - occlusionTex); + occlusionTex, + scattering); } diff --git a/libraries/render-utils/src/model_normal_map.slv b/libraries/render-utils/src/model_normal_map.slv index ded37923c2..9e674d93fc 100755 --- a/libraries/render-utils/src/model_normal_map.slv +++ b/libraries/render-utils/src/model_normal_map.slv @@ -22,6 +22,7 @@ out vec4 _position; out vec2 _texCoord0; +out vec2 _texCoord1; out vec3 _normal; out vec3 _tangent; out vec3 _color; @@ -34,11 +35,12 @@ void main(void) { TexMapArray texMapArray = getTexMapArray(); <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> - <$transformModelToEyeDir(cam, obj, inTangent.xyz, _tangent)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToWorldDir(cam, obj, inTangent.xyz, _tangent)$> } diff --git a/libraries/render-utils/src/model_normal_specular_map.slf b/libraries/render-utils/src/model_normal_specular_map.slf index 2529596818..d5dd607b8f 100755 --- a/libraries/render-utils/src/model_normal_specular_map.slf +++ b/libraries/render-utils/src/model_normal_specular_map.slf @@ -21,6 +21,7 @@ in vec4 _position; in vec2 _texCoord0; +in vec2 _texCoord1; in vec3 _normal; in vec3 _tangent; in vec3 _color; @@ -28,7 +29,8 @@ in vec3 _color; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); - <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, metallicTex, emissiveTex, occlusionTex)$> + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, metallicTex, emissiveTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> float opacity = 1.0; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>; @@ -50,6 +52,7 @@ void main(void) { float metallic = getMaterialMetallic(mat); <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; + float scattering = getMaterialScattering(mat); packDeferredFragment( normalize(viewNormal.xyz), @@ -58,5 +61,6 @@ void main(void) { roughness, metallic, emissive, - occlusionTex); + occlusionTex, + scattering); } diff --git a/libraries/render-utils/src/model_specular_map.slf b/libraries/render-utils/src/model_specular_map.slf index 3cbb060ab5..47b5e3389d 100755 --- a/libraries/render-utils/src/model_specular_map.slf +++ b/libraries/render-utils/src/model_specular_map.slf @@ -21,6 +21,7 @@ in vec4 _position; in vec2 _texCoord0; +in vec2 _texCoord1; in vec3 _normal; in vec3 _color; @@ -28,7 +29,8 @@ in vec3 _color; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); - <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex, occlusionTex)$> + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> float opacity = 1.0; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; @@ -47,6 +49,8 @@ void main(void) { float metallic = getMaterialMetallic(mat); <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; + float scattering = getMaterialScattering(mat); + packDeferredFragment( normalize(_normal), opacity, @@ -54,5 +58,6 @@ void main(void) { roughness, metallic, emissive, - occlusionTex); + occlusionTex, + scattering); } diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 27a22a9763..6cf99a68ef 100755 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -25,6 +25,7 @@ <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> in vec2 _texCoord0; +in vec2 _texCoord1; in vec4 _position; in vec3 _normal; in vec3 _color; @@ -35,7 +36,8 @@ out vec4 _fragColor; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); - <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$> + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> float opacity = getMaterialOpacity(mat) * _alpha; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; @@ -48,11 +50,17 @@ void main(void) { <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; float metallic = getMaterialMetallic(mat); + vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value + if (metallic <= 0.5) { + metallic = 0.0; + } else { + fresnel = albedo; + metallic = 1.0; + } vec3 emissive = getMaterialEmissive(mat); <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; - vec3 fragPosition = _position.xyz; vec3 fragNormal = normalize(_normal); @@ -61,10 +69,11 @@ void main(void) { _fragColor = vec4(evalGlobalLightingAlphaBlended( cam._viewInverse, 1.0, - 1.0, + occlusionTex, fragPosition, fragNormal, albedo, + fresnel, metallic, emissive, roughness, opacity), diff --git a/libraries/render-utils/src/model_translucent_unlit.slf b/libraries/render-utils/src/model_translucent_unlit.slf index b9d6c64d6f..b397cea5aa 100644 --- a/libraries/render-utils/src/model_translucent_unlit.slf +++ b/libraries/render-utils/src/model_translucent_unlit.slf @@ -16,6 +16,7 @@ <@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> +<@include LightingModel.slh@> in vec2 _texCoord0; in vec3 _color; @@ -26,7 +27,7 @@ out vec4 _fragColor; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); - <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$> + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$> float opacity = getMaterialOpacity(mat) * _alpha; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; @@ -35,5 +36,5 @@ void main(void) { <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; albedo *= _color; - _fragColor = vec4(albedo, opacity); + _fragColor = vec4(albedo * isUnlitEnabled(), opacity); } diff --git a/libraries/render-utils/src/model_unlit.slf b/libraries/render-utils/src/model_unlit.slf index 50778153fb..750b51fe8c 100644 --- a/libraries/render-utils/src/model_unlit.slf +++ b/libraries/render-utils/src/model_unlit.slf @@ -13,10 +13,11 @@ // <@include DeferredBufferWrite.slh@> +<@include LightingModel.slh@> <@include model/Material.slh@> <@include MaterialTextures.slh@> -<$declareMaterialTextures(ALBEDO, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$> +<$declareMaterialTextures(ALBEDO)$> in vec2 _texCoord0; in vec3 _normal; @@ -27,7 +28,7 @@ void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); - <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$> + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$> float opacity = 1.0; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; @@ -40,5 +41,5 @@ void main(void) { packDeferredFragmentUnlit( normalize(_normal), opacity, - albedo); + albedo * isUnlitEnabled()); } diff --git a/libraries/render-utils/src/overlay3D.slf b/libraries/render-utils/src/overlay3D.slf index 38f236e0b3..b29b9baf64 100644 --- a/libraries/render-utils/src/overlay3D.slf +++ b/libraries/render-utils/src/overlay3D.slf @@ -11,13 +11,18 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include DeferredLighting.slh@> + <@include model/Light.slh@> +<@include LightingModel.slh@> + +<@include LightDirectional.slh@> +<$declareLightingDirectional()$> + <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> -vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 specular, float roughness, float opacity) { +vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 fresnel, float roughness, float opacity) { // Need the light now Light light = getLight(); @@ -30,9 +35,12 @@ vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 a vec3 color = opacity * albedo * getLightColor(light) * getLightAmbientIntensity(light); - vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, metallic, specular, roughness); - - color += vec3(albedo * shading.w * opacity + shading.rgb) * shadowAttenuation * getLightColor(light) * getLightIntensity(light); + // Directional + vec3 directionalDiffuse; + vec3 directionalSpecular; + evalLightingDirectional(directionalDiffuse, directionalSpecular, light, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation); + color += directionalDiffuse * isDiffuseEnabled() * isDirectionalEnabled(); + color += directionalSpecular * isSpecularEnabled() * isDirectionalEnabled(); return vec4(color, opacity); } diff --git a/libraries/render-utils/src/overlay3D.slv b/libraries/render-utils/src/overlay3D.slv index d39e5a2f01..ee28367413 100644 --- a/libraries/render-utils/src/overlay3D.slv +++ b/libraries/render-utils/src/overlay3D.slv @@ -32,5 +32,5 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> } diff --git a/libraries/render-utils/src/overlay3D_translucent.slf b/libraries/render-utils/src/overlay3D_translucent.slf index f8c18abf20..ce5bbf3589 100644 --- a/libraries/render-utils/src/overlay3D_translucent.slf +++ b/libraries/render-utils/src/overlay3D_translucent.slf @@ -12,13 +12,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include DeferredLighting.slh@> <@include model/Light.slh@> +<@include LightingModel.slh@> + +<@include LightDirectional.slh@> +<$declareLightingDirectional()$> + <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> -vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 specular, float roughness, float opacity) { +vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 fresnel, float roughness, float opacity) { // Need the light now Light light = getLight(); @@ -31,9 +35,12 @@ vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 a vec3 color = opacity * albedo * getLightColor(light) * getLightAmbientIntensity(light); - vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, metallic, specular, roughness); - - color += vec3(albedo * shading.w * opacity + shading.rgb) * shadowAttenuation * getLightColor(light) * getLightIntensity(light); + // Directional + vec3 directionalDiffuse; + vec3 directionalSpecular; + evalLightingDirectional(directionalDiffuse, directionalSpecular, light, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation); + color += directionalDiffuse; + color += directionalSpecular / opacity; return vec4(color, opacity); } diff --git a/libraries/render-utils/src/point_light.slf b/libraries/render-utils/src/point_light.slf index fc72f094e7..80b2e62b8a 100644 --- a/libraries/render-utils/src/point_light.slf +++ b/libraries/render-utils/src/point_light.slf @@ -5,7 +5,7 @@ // point_light.frag // fragment shader // -// Created by Andrzej Kapolka on 9/18/14. +// Created by Sam Gateau on 9/18/15. // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -15,29 +15,36 @@ // Everything about deferred buffer <@include DeferredBufferRead.slh@> -//Everything about deferred lighting -<@include DeferredLighting.slh@> +<$declareDeferredCurvature()$> // Everything about light <@include model/Light.slh@> +<@include LightingModel.slh@> + +<@include LightPoint.slh@> +<$declareLightingPoint(supportScattering)$> + + +uniform vec4 texcoordFrameTransform; in vec4 _texCoord0; out vec4 _fragColor; void main(void) { - DeferredTransform deferredTransform = getDeferredTransform(); + DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); // Grab the fragment data from the uv vec2 texCoord = _texCoord0.st / _texCoord0.q; + texCoord *= texcoordFrameTransform.zw; + texCoord += texcoordFrameTransform.xy; + DeferredFragment frag = unpackDeferredFragment(deferredTransform, texCoord); if (frag.mode == FRAG_MODE_UNLIT) { discard; } - mat4 invViewMat = deferredTransform.viewInverse; - // Kill if in front of the light volume float depth = frag.depthVal; if (depth < gl_FragCoord.z) { @@ -47,38 +54,33 @@ void main(void) { // Need the light now Light light = getLight(); - // Make the Light vector going from fragment to light center in world space + // Frag pos in world + mat4 invViewMat = getViewInverse(); vec4 fragPos = invViewMat * frag.position; - vec3 fragLightVec = getLightPosition(light) - fragPos.xyz; - // Kill if too far from the light center - if (dot(fragLightVec, fragLightVec) > getLightCutoffSquareRadius(light)) { + // Clip againgst the light volume and Make the Light vector going from fragment to light center in world space + vec4 fragLightVecLen2; + if (!clipFragToLightVolumePoint(light, fragPos.xyz, fragLightVecLen2)) { discard; } - // Allright we re valid in the volume - float fragLightDistance = length(fragLightVec); - vec3 fragLightDir = fragLightVec / fragLightDistance; - - // Eval shading - vec3 fragNormal = vec3(invViewMat * vec4(frag.normal, 0.0)); + // Frag to eye vec vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0); vec3 fragEyeDir = normalize(fragEyeVector.xyz); - vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.metallic, frag.specular, frag.roughness); - // Eval attenuation - float radialAttenuation = evalLightAttenuation(light, fragLightDistance); - // Final Lighting color - vec3 fragColor = (shading.w * frag.diffuse + shading.xyz); - _fragColor = vec4(fragColor * radialAttenuation * getLightColor(light) * getLightIntensity(light) * frag.obscurance, 0.0); - - if (getLightShowContour(light) > 0.0) { - // Show edge - float edge = abs(2.0 * ((getLightRadius(light) - fragLightDistance) / (0.1)) - 1.0); - if (edge < 1) { - float edgeCoord = exp2(-8.0*edge*edge); - _fragColor = vec4(edgeCoord * edgeCoord * getLightShowContour(light) * getLightColor(light), 0.0); - } + vec3 diffuse; + vec3 specular; + vec4 midNormalCurvature; + vec4 lowNormalCurvature; + if (frag.mode == FRAG_MODE_SCATTERING) { + unpackMidLowNormalCurvature(texCoord, midNormalCurvature, lowNormalCurvature); } + evalLightingPoint(diffuse, specular, light, + fragLightVecLen2.xyz, fragEyeDir, frag.normal, frag.roughness, + frag.metallic, frag.fresnel, frag.albedo, 1.0, + frag.scattering, midNormalCurvature, lowNormalCurvature); + + _fragColor.rgb += diffuse; + _fragColor.rgb += specular; } diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv index d8b7587789..29bc1a9e85 100644 --- a/libraries/render-utils/src/sdf_text3D.slv +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -27,5 +27,5 @@ void main() { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal.xyz)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal.xyz)$> } \ No newline at end of file diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index 0f848ee231..dd5faf40db 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -41,7 +41,7 @@ void main(void) { #ifdef PROCEDURAL_V1 specular = getProceduralColor().rgb; // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - specular = pow(specular, vec3(2.2)); + //specular = pow(specular, vec3(2.2)); emissiveAmount = 1.0; #else emissiveAmount = getProceduralColors(diffuse, specular, shininess); @@ -54,6 +54,6 @@ void main(void) { normal, 1.0, diffuse, max(0, 1.0 - shininess / 128.0), DEFAULT_METALLIC, specular, specular); } else { packDeferredFragment( - normal, 1.0, diffuse, max(0, 1.0 - shininess / 128.0), length(specular), DEFAULT_EMISSIVE, DEFAULT_OCCLUSION); + normal, 1.0, diffuse, max(0, 1.0 - shininess / 128.0), length(specular), DEFAULT_EMISSIVE, DEFAULT_OCCLUSION, DEFAULT_SCATTERING); } } diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv index d56d1cc8e2..64d3e24192 100644 --- a/libraries/render-utils/src/simple.slv +++ b/libraries/render-utils/src/simple.slv @@ -34,5 +34,5 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> } \ No newline at end of file diff --git a/libraries/render-utils/src/simple_textured.slf b/libraries/render-utils/src/simple_textured.slf index 062fb96f7d..f045af2ce5 100644 --- a/libraries/render-utils/src/simple_textured.slf +++ b/libraries/render-utils/src/simple_textured.slf @@ -36,5 +36,6 @@ void main(void) { DEFAULT_ROUGHNESS, DEFAULT_METALLIC, DEFAULT_EMISSIVE, - DEFAULT_OCCLUSION); + DEFAULT_OCCLUSION, + DEFAULT_SCATTERING); } \ No newline at end of file diff --git a/libraries/render-utils/src/skin_model.slv b/libraries/render-utils/src/skin_model.slv index c8501b8ddf..4236508edb 100755 --- a/libraries/render-utils/src/skin_model.slv +++ b/libraries/render-utils/src/skin_model.slv @@ -24,6 +24,7 @@ out vec4 _position; out vec2 _texCoord0; +out vec2 _texCoord1; out vec3 _normal; out vec3 _color; out float _alpha; @@ -40,11 +41,11 @@ void main(void) { TexMapArray texMapArray = getTexMapArray(); <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> - <$transformModelToEyeDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> - _normal = interpolatedNormal.xyz; + <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, _normal.xyz)$> } diff --git a/libraries/render-utils/src/skin_model_normal_map.slv b/libraries/render-utils/src/skin_model_normal_map.slv index db4b206405..9f1087f87a 100755 --- a/libraries/render-utils/src/skin_model_normal_map.slv +++ b/libraries/render-utils/src/skin_model_normal_map.slv @@ -24,6 +24,7 @@ out vec4 _position; out vec2 _texCoord0; +out vec2 _texCoord1; out vec3 _normal; out vec3 _tangent; out vec3 _color; @@ -42,6 +43,7 @@ void main(void) { TexMapArray texMapArray = getTexMapArray(); <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); @@ -50,8 +52,8 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> - <$transformModelToEyeDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> - <$transformModelToEyeDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$> + <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> + <$transformModelToWorldDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$> _normal = interpolatedNormal.xyz; _tangent = interpolatedTangent.xyz; diff --git a/libraries/render-utils/src/spot_light.slf b/libraries/render-utils/src/spot_light.slf index 4191ba3f63..4d4ad71413 100644 --- a/libraries/render-utils/src/spot_light.slf +++ b/libraries/render-utils/src/spot_light.slf @@ -5,7 +5,7 @@ // spot_light.frag // fragment shader // -// Created by Andrzej Kapolka on 9/18/14. +// Created by Sam Gateau on 9/18/15. // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -15,29 +15,36 @@ // Everything about deferred buffer <@include DeferredBufferRead.slh@> -//Everything about deferred lighting -<@include DeferredLighting.slh@> +<$declareDeferredCurvature()$> // Everything about light <@include model/Light.slh@> +<@include LightingModel.slh@> + +<@include LightSpot.slh@> +<$declareLightingSpot(supportScattering)$> + +uniform vec4 texcoordFrameTransform; + in vec4 _texCoord0; out vec4 _fragColor; void main(void) { - DeferredTransform deferredTransform = getDeferredTransform(); + DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); // Grab the fragment data from the uv vec2 texCoord = _texCoord0.st / _texCoord0.q; + texCoord *= texcoordFrameTransform.zw; + texCoord += texcoordFrameTransform.xy; + DeferredFragment frag = unpackDeferredFragment(deferredTransform, texCoord); if (frag.mode == FRAG_MODE_UNLIT) { discard; } - mat4 invViewMat = deferredTransform.viewInverse; - // Kill if in front of the light volume float depth = frag.depthVal; if (depth < gl_FragCoord.z) { @@ -47,50 +54,36 @@ void main(void) { // Need the light now Light light = getLight(); - // Make the Light vector going from fragment to light center in world space + // Frag pos in world + mat4 invViewMat = getViewInverse(); vec4 fragPos = invViewMat * frag.position; - vec3 fragLightVec = getLightPosition(light) - fragPos.xyz; - // Kill if too far from the light center - if (dot(fragLightVec, fragLightVec) > getLightCutoffSquareRadius(light)) { + // Clip againgst the light volume and Make the Light vector going from fragment to light center in world space + vec4 fragLightVecLen2; + vec4 fragLightDirLen; + float cosSpotAngle; + if (!clipFragToLightVolumeSpot(light, fragPos.xyz, fragLightVecLen2, fragLightDirLen, cosSpotAngle)) { discard; } - // Allright we re valid in the volume - float fragLightDistance = length(fragLightVec); - vec3 fragLightDir = fragLightVec / fragLightDistance; - - // Kill if not in the spot light (ah ah !) - vec3 lightSpotDir = getLightDirection(light); - float cosSpotAngle = max(-dot(fragLightDir, lightSpotDir), 0.0); - if (cosSpotAngle < getLightSpotAngleCos(light)) { - discard; - } - - // Eval shading - vec3 fragNormal = vec3(invViewMat * vec4(frag.normal, 0.0)); + // Frag to eye vec vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0); vec3 fragEyeDir = normalize(fragEyeVector.xyz); - vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.metallic, frag.specular, frag.roughness); - - // Eval attenuation - float radialAttenuation = evalLightAttenuation(light, fragLightDistance); - float angularAttenuation = evalLightSpotAttenuation(light, cosSpotAngle); - // Final Lighting color - vec3 fragColor = (shading.w * frag.diffuse + shading.xyz); - _fragColor = vec4(fragColor * angularAttenuation * radialAttenuation * getLightColor(light) * getLightIntensity(light) * frag.obscurance, 0.0); - if (getLightShowContour(light) > 0.0) { - // Show edges - float edgeDistR = (getLightRadius(light) - fragLightDistance); - float edgeDistS = dot(fragLightDistance * vec2(cosSpotAngle, sqrt(1.0 - cosSpotAngle * cosSpotAngle)), -getLightSpotOutsideNormal2(light)); - float edgeDist = min(edgeDistR, edgeDistS); - float edge = abs(2.0 * (edgeDist / (0.1)) - 1.0); - if (edge < 1) { - float edgeCoord = exp2(-8.0*edge*edge); - _fragColor = vec4(edgeCoord * edgeCoord * getLightColor(light), 0.0); - } + vec3 diffuse; + vec3 specular; + vec4 midNormalCurvature; + vec4 lowNormalCurvature; + if (frag.mode == FRAG_MODE_SCATTERING) { + unpackMidLowNormalCurvature(texCoord, midNormalCurvature, lowNormalCurvature); } + evalLightingSpot(diffuse, specular, light, + fragLightDirLen.xyzw, cosSpotAngle, fragEyeDir, frag.normal, frag.roughness, + frag.metallic, frag.fresnel, frag.albedo, 1.0, + frag.scattering, midNormalCurvature, lowNormalCurvature); + + _fragColor.rgb += diffuse; + _fragColor.rgb += specular; } diff --git a/libraries/render-utils/src/ssao_makeOcclusion.slf b/libraries/render-utils/src/ssao_makeOcclusion.slf index 72424cad1e..01e44d0bb9 100644 --- a/libraries/render-utils/src/ssao_makeOcclusion.slf +++ b/libraries/render-utils/src/ssao_makeOcclusion.slf @@ -112,7 +112,7 @@ void main(void) { // From now on, ssC is the pixel pos in the side ssC.x -= side.y; - vec2 fragPos = (vec2(ssC) + 0.5) / getStereoSideWidth(); + vec2 fragPos = (vec2(ssC) + 0.5) / getStereoSideWidth(); // The position and normal of the pixel fragment in Eye space vec3 Cp = evalEyePositionFromZeye(side.x, Zeye, fragPos); diff --git a/libraries/render-utils/src/standardTransformPNTC.slv b/libraries/render-utils/src/standardTransformPNTC.slv index f7e72b6997..0ced5ba6e2 100644 --- a/libraries/render-utils/src/standardTransformPNTC.slv +++ b/libraries/render-utils/src/standardTransformPNTC.slv @@ -30,6 +30,6 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, varNormal)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, varNormal)$> varPosition = inPosition.xyz; } \ No newline at end of file diff --git a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf new file mode 100644 index 0000000000..7ee65e27e3 --- /dev/null +++ b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf @@ -0,0 +1,108 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/8/16. +// Copyright 2016 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 DeferredBufferRead.slh@> +<@include model/Light.slh@> + +<$declareDeferredCurvature()$> + +<@include SubsurfaceScattering.slh@> +<$declareSubsurfaceScatteringBRDF()$> + +in vec2 varTexCoord0; +out vec4 _fragColor; + +uniform vec2 uniformCursorTexcoord = vec2(0.5); + +//uniform vec3 uniformLightVector = vec3(1.0); + +vec3 evalScatteringBRDF(vec2 texcoord) { + DeferredFragment fragment = unpackDeferredFragmentNoPosition(texcoord); + + vec3 normal = fragment.normal; // .getWorldNormal(varTexCoord0); + vec4 blurredCurvature = fetchCurvature(texcoord); + vec4 diffusedCurvature = fetchDiffusedCurvature(texcoord); + vec3 midNormal = normalize((blurredCurvature.xyz - 0.5f) * 2.0f); + vec3 lowNormal = normalize((diffusedCurvature.xyz - 0.5f) * 2.0f); + float curvature = unpackCurvature(diffusedCurvature.w); + + + // Transform directions to worldspace + vec3 fragNormal = vec3((normal)); + + // Get light + Light light = getLight(); + vec3 fresnel = vec3(0.028); // Default Di-electric fresnel value for skin + float metallic = 0.0; + + vec3 fragLightDir = -normalize(getLightDirection(light)); + + + vec3 brdf = evalSkinBRDF(fragLightDir, fragNormal, midNormal, lowNormal, curvature); + + return brdf; +} + +vec3 drawScatteringTableUV(vec2 cursor, vec2 texcoord) { + DeferredFragment fragment = unpackDeferredFragmentNoPosition(cursor); + + vec3 normal = fragment.normal; // .getWorldNormal(varTexCoord0); + vec4 blurredCurvature = fetchCurvature(cursor); + vec4 diffusedCurvature = fetchDiffusedCurvature(cursor); + vec3 midNormal = normalize((blurredCurvature.xyz - 0.5f) * 2.0f); + vec3 lowNormal = normalize((diffusedCurvature.xyz - 0.5f) * 2.0f); + float curvature = unpackCurvature(diffusedCurvature.w); + + // Get light + Light light = getLight(); + vec3 fresnel = vec3(0.028); // Default Di-electric fresnel value for skin + + vec3 fragLightDir = -normalize(getLightDirection(light)); + + vec3 bentNdotL = evalScatteringBentNdotL(normal, midNormal, lowNormal, fragLightDir); + + // return clamp(bentNdotL * 0.5 + 0.5, 0.0, 1.0); + + vec3 distance = vec3(0.0); + for (int c = 0; c < 3; c++) { + vec2 BRDFuv = vec2(clamp(bentNdotL[c] * 0.5 + 0.5, 0.0, 1.0), clamp(2 * curvature, 0.0, 1.0)); + vec2 delta = BRDFuv - texcoord; + distance[c] = 1.0 - dot(delta, delta); + } + + distance *= distance; + + float threshold = 0.999; + vec3 color = vec3(0.0); + bool keep = false; + for (int c = 0; c < 3; c++) { + if (distance[c] > threshold) { + keep = true; + color[c] += 1.0; + } + } + + if (!keep) + discard; + + return color; +} + +void main(void) { + // _fragColor = vec4(evalScatteringBRDF(varTexCoord0), 1.0); + // _fragColor = vec4(uniformCursorTexcoord, 0.0, 1.0); + + _fragColor = vec4(drawScatteringTableUV(uniformCursorTexcoord, varTexCoord0), 1.0); +} + + diff --git a/libraries/render-utils/src/subsurfaceScattering_makeLUT.slf b/libraries/render-utils/src/subsurfaceScattering_makeLUT.slf new file mode 100644 index 0000000000..1cbf599558 --- /dev/null +++ b/libraries/render-utils/src/subsurfaceScattering_makeLUT.slf @@ -0,0 +1,27 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/8/16. +// Copyright 2016 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 SubsurfaceScattering.slh@> +<$declareSubsurfaceScatteringProfileSource()$> +<$declareSubsurfaceScatteringIntegrate(2000)$> + +in vec2 varTexCoord0; +out vec4 outFragColor; + +void main(void) { + + // Lookup by: x: NDotL y: 1 / r + //float y = 2.0 * 1.0 / ((j + 1.0) / (double)height); + //float x = ((i / (double)width) * 2.0) - 1.0; + + outFragColor = vec4(integrate(varTexCoord0.x * 2.0 - 1.0, 2.0 / varTexCoord0.y), 1.0); +} + diff --git a/libraries/render-utils/src/subsurfaceScattering_makeProfile.slf b/libraries/render-utils/src/subsurfaceScattering_makeProfile.slf new file mode 100644 index 0000000000..ea4a864448 --- /dev/null +++ b/libraries/render-utils/src/subsurfaceScattering_makeProfile.slf @@ -0,0 +1,20 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/27/16. +// Copyright 2016 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 SubsurfaceScattering.slh@> +<$declareSubsurfaceScatteringGenerateProfileMap()$> + +in vec2 varTexCoord0; +out vec4 outFragColor; + +void main(void) { + outFragColor = vec4(generateProfile(varTexCoord0.xy), 1.0); +} diff --git a/libraries/render-utils/src/subsurfaceScattering_makeSpecularBeckmann.slf b/libraries/render-utils/src/subsurfaceScattering_makeSpecularBeckmann.slf new file mode 100644 index 0000000000..f10d287c35 --- /dev/null +++ b/libraries/render-utils/src/subsurfaceScattering_makeSpecularBeckmann.slf @@ -0,0 +1,26 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/30/16. +// Copyright 2016 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 +// + + + +in vec2 varTexCoord0; +out vec4 outFragColor; + +float specularBeckmann(float ndoth, float roughness) { + float alpha = acos(ndoth); + float ta = tan(alpha); + float val = 1.0 / (roughness * roughness * pow(ndoth, 4.0)) * exp(-(ta * ta) / (roughness * roughness)); + return val; +} + +void main(void) { + outFragColor = vec4(vec3(0.5 * pow( specularBeckmann(varTexCoord0.x, varTexCoord0.y), 0.1)), 1.0); +} diff --git a/libraries/render-utils/src/surfaceGeometry_downsampleDepthNormal.slf b/libraries/render-utils/src/surfaceGeometry_downsampleDepthNormal.slf new file mode 100644 index 0000000000..533073b224 --- /dev/null +++ b/libraries/render-utils/src/surfaceGeometry_downsampleDepthNormal.slf @@ -0,0 +1,47 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/3/16. +// Copyright 2016 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 gpu/PackedNormal.slh@> + +uniform sampler2D linearDepthMap; +uniform sampler2D normalMap; + +in vec2 varTexCoord0; + +out vec4 outLinearDepth; +out vec4 outNormal; + +void main(void) { + // Gather 2 by 2 quads from texture + + // Try different filters for Z +// vec4 Zeyes = textureGather(linearDepthMap, varTexCoord0, 0); + // float Zeye = min(min(Zeyes.x, Zeyes.y), min(Zeyes.z, Zeyes.w)); + float Zeye = texture(linearDepthMap, varTexCoord0).x; + + vec4 rawNormalsX = textureGather(normalMap, varTexCoord0, 0); + vec4 rawNormalsY = textureGather(normalMap, varTexCoord0, 1); + vec4 rawNormalsZ = textureGather(normalMap, varTexCoord0, 2); + + + vec3 normal = vec3(0.0); + normal += unpackNormal(vec3(rawNormalsX[0], rawNormalsY[0], rawNormalsZ[0])); + normal += unpackNormal(vec3(rawNormalsX[1], rawNormalsY[1], rawNormalsZ[1])); + normal += unpackNormal(vec3(rawNormalsX[2], rawNormalsY[2], rawNormalsZ[2])); + normal += unpackNormal(vec3(rawNormalsX[3], rawNormalsY[3], rawNormalsZ[3])); + + normal = normalize(normal); + + outLinearDepth = vec4(Zeye, 0.0, 0.0, 0.0); + outNormal = vec4((normal + vec3(1.0)) * 0.5, 0.0); +} + diff --git a/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf b/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf new file mode 100644 index 0000000000..e96ac60b45 --- /dev/null +++ b/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf @@ -0,0 +1,232 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/3/16. +// Copyright 2016 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 DeferredTransform.slh@> +<$declareDeferredFrameTransform()$> + +<@include gpu/PackedNormal.slh@> + +struct SurfaceGeometryParams { + // Resolution info + vec4 resolutionInfo; + // Curvature algorithm + vec4 curvatureInfo; +}; + +uniform surfaceGeometryParamsBuffer { + SurfaceGeometryParams params; +}; + +float getCurvatureDepthThreshold() { + return params.curvatureInfo.x; +} + +float getCurvatureBasisScale() { + return params.curvatureInfo.y; +} + +float getCurvatureScale() { + return params.curvatureInfo.w; +} + +bool isFullResolution() { + return params.resolutionInfo.w == 0.0; +} + + +uniform sampler2D linearDepthMap; +float getZEye(ivec2 pixel) { + return -texelFetch(linearDepthMap, pixel, 0).x; +} +float getZEyeLinear(vec2 texcoord) { + return -texture(linearDepthMap, texcoord).x; +} + +vec2 sideToFrameTexcoord(vec2 side, vec2 texcoordPos) { + return vec2((texcoordPos.x + side.x) * side.y, texcoordPos.y); +} + +uniform sampler2D normalMap; + +vec3 getRawNormal(vec2 texcoord) { + return texture(normalMap, texcoord).xyz; +} + +vec3 getWorldNormal(vec2 texcoord) { + vec3 rawNormal = getRawNormal(texcoord); + if (isFullResolution()) { + return unpackNormal(rawNormal); + } else { + return normalize((rawNormal - vec3(0.5)) * 2.0); + } +} + +vec3 getWorldNormalDiff(vec2 texcoord, vec2 delta) { + return getWorldNormal(texcoord + delta) - getWorldNormal(texcoord - delta); +} + +float getEyeDepthDiff(vec2 texcoord, vec2 delta) { + return getZEyeLinear(texcoord + delta) - getZEyeLinear(texcoord - delta); +} + + + +in vec2 varTexCoord0; +out vec4 outFragColor; + +void main(void) { + // Pixel being shaded + ivec2 pixelPos; + vec2 texcoordPos; + ivec4 stereoSide; + ivec2 framePixelPos = getPixelPosTexcoordPosAndSide(gl_FragCoord.xy, pixelPos, texcoordPos, stereoSide); + vec2 stereoSideClip = vec2(stereoSide.x, (isStereo() ? 0.5 : 1.0)); + + // Texcoord to fetch in the deferred texture are the exact UVs comming from vertex shader + // sideToFrameTexcoord(stereoSideClip, texcoordPos); + vec2 frameTexcoordPos = varTexCoord0; + + // Fetch the z under the pixel (stereo or not) + float Zeye = getZEye(framePixelPos); + if (Zeye <= -getPosLinearDepthFar()) { + outFragColor = vec4(1.0, 0.0, 0.0, 0.0); + return; + } + + float nearPlaneScale = 0.5 * getProjectionNear(); + + vec3 worldNormal = getWorldNormal(frameTexcoordPos); + + // The position of the pixel fragment in Eye space then in world space + vec3 eyePos = evalEyePositionFromZeye(stereoSide.x, Zeye, texcoordPos); + // vec3 worldPos = (frameTransform._viewInverse * vec4(eyePos, 1.0)).xyz; + + /* if (texcoordPos.y > 0.5) { + outFragColor = vec4(fract(10.0 * worldPos.xyz), 1.0); + } else { + outFragColor = vec4(fract(10.0 * eyePos.xyz), 1.0); + }*/ + // return; + + // Calculate the perspective scale. + // Clamp to 0.5 + // float perspectiveScale = max(0.5, (-getProjScaleEye() / Zeye)); + float perspectiveScale = max(0.5, (-getCurvatureBasisScale() * getProjectionNear() / Zeye)); + + // Calculate dF/du and dF/dv + vec2 viewportScale = perspectiveScale * getInvWidthHeight(); + vec2 du = vec2( viewportScale.x * (stereoSide.w > 0.0 ? 0.5 : 1.0), 0.0f ); + vec2 dv = vec2( 0.0f, viewportScale.y ); + + vec4 dFdu = vec4(getWorldNormalDiff(frameTexcoordPos, du), getEyeDepthDiff(frameTexcoordPos, du)); + vec4 dFdv = vec4(getWorldNormalDiff(frameTexcoordPos, dv), getEyeDepthDiff(frameTexcoordPos, dv)); + + float threshold = getCurvatureDepthThreshold(); + dFdu *= step(abs(dFdu.w), threshold); + dFdv *= step(abs(dFdv.w), threshold); + + // Calculate ( du/dx, du/dy, du/dz ) and ( dv/dx, dv/dy, dv/dz ) + // Eval px, py, pz world positions of the basis centered on the world pos of the fragment + float axeLength = nearPlaneScale; + + vec3 ax = (frameTransform._view[0].xyz * axeLength); + vec3 ay = (frameTransform._view[1].xyz * axeLength); + vec3 az = (frameTransform._view[2].xyz * axeLength); + + vec4 px = vec4(eyePos + ax, 0.0); + vec4 py = vec4(eyePos + ay, 0.0); + vec4 pz = vec4(eyePos + az, 0.0); + + + /* if (texcoordPos.y > 0.5) { + outFragColor = vec4(fract(px.xyz), 1.0); + } else { + outFragColor = vec4(fract(eyePos.xyz), 1.0); + }*/ + // return; + + + /* IN case the axis end point goes behind mid way near plane, this shouldn't happen + if (px.z >= -nearPlaneScale) { + outFragColor = vec4(1.0, 0.0, 0.0, 1.0); + return; + } else if (py.z >= -nearPlaneScale) { + outFragColor = vec4(0.0, 1.0, 0.0, 1.0); + return; + } else if (pz.z >= -nearPlaneScale) { + outFragColor = vec4(0.0, 0.0, 1.0, 1.0); + return; + }*/ + + + // Project px, py pz to homogeneous clip space + // mat4 viewProj = getProjection(stereoSide.x); + mat4 viewProj = getProjectionMono(); + px = viewProj * px; + py = viewProj * py; + pz = viewProj * pz; + + + // then to normalized clip space + px.xy /= px.w; + py.xy /= py.w; + pz.xy /= pz.w; + + vec2 nclipPos = (texcoordPos - 0.5) * 2.0; + + + //vec4 clipPos = frameTransform._projection[stereoSide.x] * vec4(eyePos, 1.0); + vec4 clipPos = getProjectionMono() * vec4(eyePos, 1.0); + nclipPos = clipPos.xy / clipPos.w; + + /* if (texcoordPos.y > 0.5) { + // outFragColor = vec4(fract(10.0 * worldPos.xyz), 1.0); + outFragColor = vec4(fract(10.0 * (nclipPos)), 0.0, 1.0); + + } else { + outFragColor = vec4(fract(10.0 * (clipPos.xy / clipPos.w)), 0.0, 1.0); + // outFragColor = vec4(nclipPos * 0.5 + 0.5, 0.0, 1.0); + }*/ + //return; + + + float pixPerspectiveScaleInv = 1.0 / (perspectiveScale); + px.xy = (px.xy - nclipPos) * pixPerspectiveScaleInv; + py.xy = (py.xy - nclipPos) * pixPerspectiveScaleInv; + pz.xy = (pz.xy - nclipPos) * pixPerspectiveScaleInv; + + /* if (texcoordPos.y > 0.5) { + // outFragColor = vec4(fract(10.0 * worldPos.xyz), 1.0); + outFragColor = vec4(fract(10.0 * (px.xy)), 0.0, 1.0); + + } else { + outFragColor = vec4(fract(10.0 * (py.xy)), 0.0, 1.0); + // outFragColor = vec4(nclipPos * 0.5 + 0.5, 0.0, 1.0); + }*/ + // return; + + // Calculate dF/dx, dF/dy and dF/dz using chain rule + vec4 dFdx = dFdu * px.x + dFdv * px.y; + vec4 dFdy = dFdu * py.x + dFdv * py.y; + vec4 dFdz = dFdu * pz.x + dFdv * pz.y; + + vec3 trace = vec3(dFdx.x, dFdy.y, dFdz.z); + + /*if (dot(trace, trace) > params.curvatureInfo.w) { + outFragColor = vec4(dFdx.x, dFdy.y, dFdz.z, 1.0); + return; + }*/ + + // Calculate the mean curvature + float meanCurvature = ((trace.x + trace.y + trace.z) * 0.33333333333333333) * params.curvatureInfo.w; + + outFragColor = vec4(vec3(worldNormal + 1.0) * 0.5, (meanCurvature + 1.0) * 0.5); +} diff --git a/libraries/render-utils/src/surfaceGeometry_makeLinearDepth.slf b/libraries/render-utils/src/surfaceGeometry_makeLinearDepth.slf new file mode 100644 index 0000000000..d512f613bc --- /dev/null +++ b/libraries/render-utils/src/surfaceGeometry_makeLinearDepth.slf @@ -0,0 +1,25 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/3/16. +// Copyright 2016 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 DeferredTransform.slh@> +<$declareDeferredFrameTransform()$> + + +uniform sampler2D depthMap; + +out vec4 outFragColor; + +void main(void) { + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + float Zeye = -evalZeyeFromZdb(Zdb); + outFragColor = vec4(Zeye, 0.0, 0.0, 1.0); +} + diff --git a/libraries/render-utils/src/toneMapping.slf b/libraries/render-utils/src/toneMapping.slf new file mode 100644 index 0000000000..d6504b5bff --- /dev/null +++ b/libraries/render-utils/src/toneMapping.slf @@ -0,0 +1,59 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on Sat Oct 24 09:34:37 2015 +// +// Draw texture 0 fetched at texcoord.xy +// +// Created by Sam Gateau on 6/22/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 +// + +struct ToneMappingParams { + vec4 _exp_2powExp_s0_s1; + ivec4 _toneCurve_s0_s1_s2; +}; + +const float INV_GAMMA_22 = 1.0 / 2.2; +const int ToneCurveNone = 0; +const int ToneCurveGamma22 = 1; +const int ToneCurveReinhard = 2; +const int ToneCurveFilmic = 3; + +uniform toneMappingParamsBuffer { + ToneMappingParams params; +}; +float getTwoPowExposure() { + return params._exp_2powExp_s0_s1.y; +} +int getToneCurve() { + return params._toneCurve_s0_s1_s2.x; +} + +uniform sampler2D colorMap; + +in vec2 varTexCoord0; +out vec4 outFragColor; + +void main(void) { + vec4 fragColorRaw = texture(colorMap, varTexCoord0); + vec3 fragColor = fragColorRaw.xyz; + + vec3 srcColor = fragColor * getTwoPowExposure(); + + int toneCurve = getToneCurve(); + vec3 tonedColor = srcColor; + if (toneCurve == ToneCurveFilmic) { + vec3 x = max(vec3(0.0), srcColor-0.004); + tonedColor = (x * (6.2 * x + 0.5)) / (x * (6.2 * x + 1.7) + 0.06); + } else if (toneCurve == ToneCurveReinhard) { + tonedColor = srcColor/(1.0 + srcColor); + tonedColor = pow(tonedColor, vec3(INV_GAMMA_22)); + } else if (toneCurve == ToneCurveGamma22) { + tonedColor = pow(srcColor, vec3(INV_GAMMA_22)); + } // else None toned = src + + outFragColor = vec4(tonedColor, 1.0); +} diff --git a/libraries/render/CMakeLists.txt b/libraries/render/CMakeLists.txt index 76fc8303ce..c5cfdf3668 100644 --- a/libraries/render/CMakeLists.txt +++ b/libraries/render/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME render) -AUTOSCRIBE_SHADER_LIB(gpu model) +AUTOSCRIBE_SHADER_LIB(gpu model procedural) setup_hifi_library() link_hifi_libraries(shared gpu model) diff --git a/libraries/render/src/render/BlurTask.cpp b/libraries/render/src/render/BlurTask.cpp new file mode 100644 index 0000000000..3849adf588 --- /dev/null +++ b/libraries/render/src/render/BlurTask.cpp @@ -0,0 +1,377 @@ +// +// BlurTask.cpp +// render/src/render +// +// Created by Sam Gateau on 6/7/16. +// Copyright 2016 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 "BlurTask.h" + +#include +#include + +#include "blurGaussianV_frag.h" +#include "blurGaussianH_frag.h" + +#include "blurGaussianDepthAwareV_frag.h" +#include "blurGaussianDepthAwareH_frag.h" + +using namespace render; + +enum BlurShaderBufferSlots { + BlurTask_ParamsSlot = 0, +}; +enum BlurShaderMapSlots { + BlurTask_SourceSlot = 0, + BlurTask_DepthSlot, +}; + +const float BLUR_NUM_SAMPLES = 7.0f; + +BlurParams::BlurParams() { + Params params; + _parametersBuffer = gpu::BufferView(std::make_shared(sizeof(Params), (const gpu::Byte*) ¶ms)); +} + +void BlurParams::setWidthHeight(int width, int height, bool isStereo) { + auto resolutionInfo = _parametersBuffer.get().resolutionInfo; + bool resChanged = false; + if (width != resolutionInfo.x || height != resolutionInfo.y) { + resChanged = true; + _parametersBuffer.edit().resolutionInfo = glm::vec4((float) width, (float) height, 1.0f / (float) width, 1.0f / (float) height); + } + + if (isStereo || resChanged) { + _parametersBuffer.edit().stereoInfo = glm::vec4((float)width, (float)height, 1.0f / (float)width, 1.0f / (float)height); + } +} + +void BlurParams::setTexcoordTransform(const glm::vec4 texcoordTransformViewport) { + auto texcoordTransform = _parametersBuffer.get().texcoordTransform; + if (texcoordTransformViewport != texcoordTransform) { + _parametersBuffer.edit().texcoordTransform = texcoordTransform; + } +} + +void BlurParams::setFilterRadiusScale(float scale) { + auto filterInfo = _parametersBuffer.get().filterInfo; + if (scale != filterInfo.x) { + _parametersBuffer.edit().filterInfo.x = scale; + _parametersBuffer.edit().filterInfo.y = scale / BLUR_NUM_SAMPLES; + } +} + +void BlurParams::setDepthPerspective(float oneOverTan2FOV) { + auto depthInfo = _parametersBuffer.get().depthInfo; + if (oneOverTan2FOV != depthInfo.w) { + _parametersBuffer.edit().depthInfo.w = oneOverTan2FOV; + } +} + +void BlurParams::setDepthThreshold(float threshold) { + auto depthInfo = _parametersBuffer.get().depthInfo; + if (threshold != depthInfo.x) { + _parametersBuffer.edit().depthInfo.x = threshold; + } +} + +void BlurParams::setLinearDepthPosFar(float farPosDepth) { + auto linearDepthInfo = _parametersBuffer.get().linearDepthInfo; + if (farPosDepth != linearDepthInfo.x) { + _parametersBuffer.edit().linearDepthInfo.x = farPosDepth; + } +} + + +BlurInOutResource::BlurInOutResource(bool generateOutputFramebuffer) : +_generateOutputFramebuffer(generateOutputFramebuffer) +{ + +} + +bool BlurInOutResource::updateResources(const gpu::FramebufferPointer& sourceFramebuffer, Resources& blurringResources) { + if (!sourceFramebuffer) { + return false; + } + + if (!_blurredFramebuffer) { + _blurredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + + // attach depthStencil if present in source + //if (sourceFramebuffer->hasDepthStencil()) { + // _blurredFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat()); + //} + auto blurringSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT); + auto blurringTarget = gpu::TexturePointer(gpu::Texture::create2D(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), blurringSampler)); + _blurredFramebuffer->setRenderBuffer(0, blurringTarget); + } else { + // it would be easier to just call resize on the bluredFramebuffer and let it work if needed but the source might loose it's depth buffer when doing so + if ((_blurredFramebuffer->getWidth() != sourceFramebuffer->getWidth()) || (_blurredFramebuffer->getHeight() != sourceFramebuffer->getHeight())) { + _blurredFramebuffer->resize(sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), sourceFramebuffer->getNumSamples()); + //if (sourceFramebuffer->hasDepthStencil()) { + // _blurredFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat()); + //} + } + } + + blurringResources.sourceTexture = sourceFramebuffer->getRenderBuffer(0); + blurringResources.blurringFramebuffer = _blurredFramebuffer; + blurringResources.blurringTexture = _blurredFramebuffer->getRenderBuffer(0); + + if (_generateOutputFramebuffer) { + // The job output the blur result in a new Framebuffer spawning here. + // Let s make sure it s ready for this + if (!_outputFramebuffer) { + _outputFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + + // attach depthStencil if present in source + /* if (sourceFramebuffer->hasDepthStencil()) { + _outputFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat()); + }*/ + auto blurringSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT); + auto blurringTarget = gpu::TexturePointer(gpu::Texture::create2D(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), blurringSampler)); + _outputFramebuffer->setRenderBuffer(0, blurringTarget); + } else { + if ((_outputFramebuffer->getWidth() != sourceFramebuffer->getWidth()) || (_outputFramebuffer->getHeight() != sourceFramebuffer->getHeight())) { + _outputFramebuffer->resize(sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), sourceFramebuffer->getNumSamples()); + /* if (sourceFramebuffer->hasDepthStencil()) { + _outputFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat()); + }*/ + } + } + + // Should be good to use the output Framebuffer as final + blurringResources.finalFramebuffer = _outputFramebuffer; + } else { + // Just the reuse the input as output to blur itself. + blurringResources.finalFramebuffer = sourceFramebuffer; + } + + return true; +} + +BlurGaussian::BlurGaussian(bool generateOutputFramebuffer) : + _inOutResources(generateOutputFramebuffer) +{ + _parameters = std::make_shared(); +} + +gpu::PipelinePointer BlurGaussian::getBlurVPipeline() { + if (!_blurVPipeline) { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(blurGaussianV_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("blurParamsBuffer"), BlurTask_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), BlurTask_SourceSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + // Stencil test the curvature pass for objects pixels only, not the background + // state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + + _blurVPipeline = gpu::Pipeline::create(program, state); + } + + return _blurVPipeline; +} + +gpu::PipelinePointer BlurGaussian::getBlurHPipeline() { + if (!_blurHPipeline) { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(blurGaussianH_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("blurParamsBuffer"), BlurTask_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), BlurTask_SourceSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + // Stencil test the curvature pass for objects pixels only, not the background + // state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + + _blurHPipeline = gpu::Pipeline::create(program, state); + } + + return _blurHPipeline; +} + +void BlurGaussian::configure(const Config& config) { + _parameters->setFilterRadiusScale(config.filterScale); +} + + +void BlurGaussian::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceFramebuffer, gpu::FramebufferPointer& blurredFramebuffer) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + RenderArgs* args = renderContext->args; + + + BlurInOutResource::Resources blurringResources; + if (!_inOutResources.updateResources(sourceFramebuffer, blurringResources)) { + // early exit if no valid blurring resources + return; + } + blurredFramebuffer = blurringResources.finalFramebuffer; + + auto blurVPipeline = getBlurVPipeline(); + auto blurHPipeline = getBlurHPipeline(); + + _parameters->setWidthHeight(args->_viewport.z, args->_viewport.w, args->_context->isStereo()); + glm::ivec2 textureSize(blurringResources.sourceTexture->getDimensions()); + _parameters->setTexcoordTransform(gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(textureSize, args->_viewport)); + + gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setViewportTransform(args->_viewport); + + batch.setUniformBuffer(BlurTask_ParamsSlot, _parameters->_parametersBuffer); + + batch.setFramebuffer(blurringResources.blurringFramebuffer); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0)); + + batch.setPipeline(blurVPipeline); + batch.setResourceTexture(BlurTask_SourceSlot, blurringResources.sourceTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + batch.setFramebuffer(blurringResources.finalFramebuffer); + if (_inOutResources._generateOutputFramebuffer) { + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0)); + } + + batch.setPipeline(blurHPipeline); + batch.setResourceTexture(BlurTask_SourceSlot, blurringResources.blurringTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + batch.setResourceTexture(BlurTask_SourceSlot, nullptr); + batch.setUniformBuffer(BlurTask_ParamsSlot, nullptr); + }); +} + + + +BlurGaussianDepthAware::BlurGaussianDepthAware(bool generateOutputFramebuffer, const BlurParamsPointer& params) : + _inOutResources(generateOutputFramebuffer), + _parameters((params ? params : std::make_shared())) +{ +} + +gpu::PipelinePointer BlurGaussianDepthAware::getBlurVPipeline() { + if (!_blurVPipeline) { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(blurGaussianDepthAwareV_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("blurParamsBuffer"), BlurTask_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), BlurTask_SourceSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), BlurTask_DepthSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + // Stencil test the curvature pass for objects pixels only, not the background + // state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + + _blurVPipeline = gpu::Pipeline::create(program, state); + } + + return _blurVPipeline; +} + +gpu::PipelinePointer BlurGaussianDepthAware::getBlurHPipeline() { + if (!_blurHPipeline) { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(blurGaussianDepthAwareH_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("blurParamsBuffer"), BlurTask_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), BlurTask_SourceSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), BlurTask_DepthSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + // Stencil test the curvature pass for objects pixels only, not the background + // state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + + _blurHPipeline = gpu::Pipeline::create(program, state); + } + + return _blurHPipeline; +} + +void BlurGaussianDepthAware::configure(const Config& config) { + _parameters->setFilterRadiusScale(config.filterScale); + _parameters->setDepthThreshold(config.depthThreshold); +} + + +void BlurGaussianDepthAware::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const Inputs& SourceAndDepth, gpu::FramebufferPointer& blurredFramebuffer) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + RenderArgs* args = renderContext->args; + + auto& sourceFramebuffer = SourceAndDepth.get0(); + auto& depthTexture = SourceAndDepth.get1(); + + BlurInOutResource::Resources blurringResources; + if (!_inOutResources.updateResources(sourceFramebuffer, blurringResources)) { + // early exit if no valid blurring resources + return; + } + + blurredFramebuffer = blurringResources.finalFramebuffer; + + auto blurVPipeline = getBlurVPipeline(); + auto blurHPipeline = getBlurHPipeline(); + + auto sourceViewport = args->_viewport; + + _parameters->setWidthHeight(sourceViewport.z, sourceViewport.w, args->_context->isStereo()); + glm::ivec2 textureSize(blurringResources.sourceTexture->getDimensions()); + _parameters->setTexcoordTransform(gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(textureSize, sourceViewport)); + _parameters->setDepthPerspective(args->getViewFrustum().getProjection()[1][1]); + _parameters->setLinearDepthPosFar(args->getViewFrustum().getFarClip()); + + gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setViewportTransform(sourceViewport); + + batch.setUniformBuffer(BlurTask_ParamsSlot, _parameters->_parametersBuffer); + + batch.setResourceTexture(BlurTask_DepthSlot, depthTexture); + + batch.setFramebuffer(blurringResources.blurringFramebuffer); + // batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0)); + + batch.setPipeline(blurVPipeline); + batch.setResourceTexture(BlurTask_SourceSlot, blurringResources.sourceTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + batch.setFramebuffer(blurringResources.finalFramebuffer); + if (_inOutResources._generateOutputFramebuffer) { + // batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0)); + } + + batch.setPipeline(blurHPipeline); + batch.setResourceTexture(BlurTask_SourceSlot, blurringResources.blurringTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + batch.setResourceTexture(BlurTask_SourceSlot, nullptr); + batch.setResourceTexture(BlurTask_DepthSlot, nullptr); + batch.setUniformBuffer(BlurTask_ParamsSlot, nullptr); + }); +} + + diff --git a/libraries/render/src/render/BlurTask.h b/libraries/render/src/render/BlurTask.h new file mode 100644 index 0000000000..a00a444af7 --- /dev/null +++ b/libraries/render/src/render/BlurTask.h @@ -0,0 +1,161 @@ +// +// BlurTask.h +// render/src/render +// +// Created by Sam Gateau on 6/7/16. +// Copyright 2016 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_render_BlurTask_h +#define hifi_render_BlurTask_h + +#include "Engine.h" + +namespace render { + + +class BlurParams { +public: + + void setWidthHeight(int width, int height, bool isStereo); + + void setTexcoordTransform(const glm::vec4 texcoordTransformViewport); + + void setFilterRadiusScale(float scale); + + void setDepthPerspective(float oneOverTan2FOV); + void setDepthThreshold(float threshold); + + void setLinearDepthPosFar(float farPosDepth); + + // Class describing the uniform buffer with all the parameters common to the blur shaders + class Params { + public: + // Resolution info (width, height, inverse of width, inverse of height) + glm::vec4 resolutionInfo{ 0.0f, 0.0f, 0.0f, 0.0f }; + + // Viewport to Texcoord info, if the region of the blur (viewport) is smaller than the full frame + glm::vec4 texcoordTransform{ 0.0f, 0.0f, 1.0f, 1.0f }; + + // Filter info (radius scale + glm::vec4 filterInfo{ 1.0f, 0.0f, 0.0f, 0.0f }; + + // Depth info (radius scale + glm::vec4 depthInfo{ 1.0f, 0.0f, 0.0f, 0.0f }; + + // stereo info if blurring a stereo render + glm::vec4 stereoInfo{ 0.0f }; + + // LinearDepth info is { f } + glm::vec4 linearDepthInfo{ 0.0f }; + + Params() {} + }; + gpu::BufferView _parametersBuffer; + + BlurParams(); +}; +using BlurParamsPointer = std::shared_ptr; + +class BlurInOutResource { +public: + BlurInOutResource(bool generateOutputFramebuffer = false); + + struct Resources { + gpu::TexturePointer sourceTexture; + gpu::FramebufferPointer blurringFramebuffer; + gpu::TexturePointer blurringTexture; + gpu::FramebufferPointer finalFramebuffer; + }; + + bool updateResources(const gpu::FramebufferPointer& sourceFramebuffer, Resources& resources); + + gpu::FramebufferPointer _blurredFramebuffer; + + // the output framebuffer defined if the job needs to output the result in a new framebuffer and not in place in th einput buffer + gpu::FramebufferPointer _outputFramebuffer; + bool _generateOutputFramebuffer{ false }; +}; + + +class BlurGaussianConfig : public Job::Config { + Q_OBJECT + Q_PROPERTY(bool enabled WRITE setEnabled READ isEnabled NOTIFY dirty) // expose enabled flag + Q_PROPERTY(float filterScale MEMBER filterScale NOTIFY dirty) // expose enabled flag +public: + + BlurGaussianConfig() : Job::Config(true) {} + + float filterScale{ 0.2f }; +signals : + void dirty(); + +protected: +}; + + +class BlurGaussian { +public: + using Config = BlurGaussianConfig; + using JobModel = Job::ModelIO; + + BlurGaussian(bool generateOutputFramebuffer = false); + + void configure(const Config& config); + void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceFramebuffer, gpu::FramebufferPointer& blurredFramebuffer); + +protected: + + BlurParamsPointer _parameters; + + gpu::PipelinePointer _blurVPipeline; + gpu::PipelinePointer _blurHPipeline; + + gpu::PipelinePointer getBlurVPipeline(); + gpu::PipelinePointer getBlurHPipeline(); + + BlurInOutResource _inOutResources; +}; + +class BlurGaussianDepthAwareConfig : public BlurGaussianConfig { + Q_OBJECT + Q_PROPERTY(float depthThreshold MEMBER depthThreshold NOTIFY dirty) // expose enabled flag +public: + BlurGaussianDepthAwareConfig() : BlurGaussianConfig() {} + + float depthThreshold{ 1.0f }; +signals: + void dirty(); +protected: +}; + +class BlurGaussianDepthAware { +public: + using Inputs = VaryingSet2; + using Config = BlurGaussianDepthAwareConfig; + using JobModel = Job::ModelIO; + + BlurGaussianDepthAware(bool generateNewOutput = false, const BlurParamsPointer& params = BlurParamsPointer()); + + void configure(const Config& config); + void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const Inputs& SourceAndDepth, gpu::FramebufferPointer& blurredFramebuffer); + + const BlurParamsPointer& getParameters() const { return _parameters; } + + gpu::PipelinePointer getBlurVPipeline(); + gpu::PipelinePointer getBlurHPipeline(); + +protected: + gpu::PipelinePointer _blurVPipeline; + gpu::PipelinePointer _blurHPipeline; + + BlurInOutResource _inOutResources; + BlurParamsPointer _parameters; +}; + +} + +#endif // hifi_render_BlurTask_h diff --git a/libraries/render/src/render/BlurTask.slh b/libraries/render/src/render/BlurTask.slh new file mode 100644 index 0000000000..de2614eb51 --- /dev/null +++ b/libraries/render/src/render/BlurTask.slh @@ -0,0 +1,167 @@ +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/7/16. +// Copyright 2016 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 +// + +<@func declareBlurUniforms()@> + +#define NUM_TAPS 7 +#define NUM_TAPS_OFFSET 3.0f + +float uniformFilterWidth = 0.05f; + +const float gaussianDistributionCurve[NUM_TAPS] = float[]( + 0.383f, 0.006f, 0.061f, 0.242f, 0.242f, 0.061f, 0.006f +); +const float gaussianDistributionOffset[NUM_TAPS] = float[]( + 0.0f, -3.0f, -2.0f, -1.0f, 1.0f, 2.0f, 3.0f +); + +struct BlurParameters { + vec4 resolutionInfo; + vec4 texcoordTransform; + vec4 filterInfo; + vec4 depthInfo; + vec4 stereoInfo; + vec4 linearDepthInfo; +}; + +uniform blurParamsBuffer { + BlurParameters parameters; +}; + +vec2 getViewportInvWidthHeight() { + return parameters.resolutionInfo.zw; +} + +vec2 evalTexcoordTransformed(vec2 texcoord) { + return (texcoord * parameters.texcoordTransform.zw + parameters.texcoordTransform.xy); +} + +float getFilterScale() { + return parameters.filterInfo.x; +} + + +float getDepthThreshold() { + return parameters.depthInfo.x; +} + +float getDepthPerspective() { + return parameters.depthInfo.w; +} + +float getPosLinearDepthFar() { + return parameters.linearDepthInfo.x; +} + +<@endfunc@> + + +<@func declareBlurGaussian()@> + +<$declareBlurUniforms()$> + +uniform sampler2D sourceMap; + +vec4 pixelShaderGaussian(vec2 texcoord, vec2 direction, vec2 pixelStep) { + texcoord = evalTexcoordTransformed(texcoord); + vec4 sampleCenter = texture(sourceMap, texcoord); + + vec2 finalStep = getFilterScale() * direction * pixelStep; + vec4 srcBlurred = vec4(0.0); + + for(int i = 0; i < NUM_TAPS; i++) { + // Fetch color and depth for current sample. + vec2 sampleCoord = texcoord + (gaussianDistributionOffset[i] * finalStep); + vec4 srcSample = texture(sourceMap, sampleCoord); + // Accumulate. + srcBlurred += gaussianDistributionCurve[i] * srcSample; + } + + return srcBlurred; +} + +<@endfunc@> + +<@func declareBlurGaussianDepthAware()@> + +<$declareBlurUniforms()$> + +uniform sampler2D sourceMap; +uniform sampler2D depthMap; + +#define NUM_HALF_TAPS 4 + +const float gaussianDistributionCurveHalf[NUM_HALF_TAPS] = float[]( + 0.383f, 0.242f, 0.061f, 0.006f +); +const float gaussianDistributionOffsetHalf[NUM_HALF_TAPS] = float[]( + 0.0f, 1.0f, 2.0f, 3.0f +); + +vec4 pixelShaderGaussianDepthAware(vec2 texcoord, vec2 direction, vec2 pixelStep) { + texcoord = evalTexcoordTransformed(texcoord); + float sampleDepth = texture(depthMap, texcoord).x; + if (sampleDepth >= getPosLinearDepthFar()) { + discard; + } + vec4 sampleCenter = texture(sourceMap, texcoord); + + // Calculate the width scale. + float distanceToProjectionWindow = getDepthPerspective(); + + float depthThreshold = getDepthThreshold(); + + // Calculate the final step to fetch the surrounding pixels. + float filterScale = getFilterScale(); + float scale = distanceToProjectionWindow / sampleDepth; + + vec2 finalStep = filterScale * scale * direction * pixelStep; + + // Accumulate the center sample + vec4 srcBlurred = gaussianDistributionCurve[0] * sampleCenter; + + for(int i = 1; i < NUM_TAPS; i++) { + // Fetch color and depth for current sample. + vec2 sampleCoord = texcoord + (gaussianDistributionOffset[i] * finalStep); + float srcDepth = texture(depthMap, sampleCoord).x; + vec4 srcSample = texture(sourceMap, sampleCoord); + + // If the difference in depth is huge, we lerp color back. + float s = clamp(depthThreshold * distanceToProjectionWindow * filterScale * abs(srcDepth - sampleDepth), 0.0, 1.0); + srcSample = mix(srcSample, sampleCenter, s); + + // Accumulate. + srcBlurred += gaussianDistributionCurve[i] * srcSample; + } + + /* + for(int i = 1; i < NUM_HALF_TAPS; i++) { + // Fetch color and depth for current sample. + vec2 texcoordOffset = (gaussianDistributionOffsetHalf[i] * finalStep); + + float srcDepthN = texture(depthMap, texcoord - texcoordOffset).x; + float srcDepthP = texture(depthMap, texcoord + texcoordOffset).x; + vec4 srcSampleN = texture(sourceMap, texcoord - texcoordOffset); + vec4 srcSampleP = texture(sourceMap, texcoord + texcoordOffset); + + // If the difference in depth is huge, we lerp color back. + float sN = clamp(depthThreshold * distanceToProjectionWindow * filterScale * abs(srcDepthN - sampleDepth), 0.0, 1.0); + float sP = clamp(depthThreshold * distanceToProjectionWindow * filterScale * abs(srcDepthP - sampleDepth), 0.0, 1.0); + + srcSampleN = mix(srcSampleN, sampleCenter, sN); + srcSampleP = mix(srcSampleP, sampleCenter, sP); + + // Accumulate. + srcBlurred += gaussianDistributionCurveHalf[i] * (srcSampleP + srcSampleN); + }*/ + + return srcBlurred; +} + +<@endfunc@> diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index e84f018e91..56729083dd 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -140,16 +140,6 @@ namespace render { int getNumItems() { return numItems; } }; - template < class T, int NUM > - class VaryingArray : public std::array { - public: - VaryingArray() { - for (size_t i = 0; i < NUM; i++) { - (*this)[i] = Varying(T()); - } - } - }; - template class MultiFilterItem { public: diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 37c8db8364..3b613e0f96 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -51,6 +51,7 @@ void ShapePlumber::addPipeline(const Key& key, const gpu::ShaderPointer& program void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& program, const gpu::StatePointer& state, BatchSetter batchSetter) { gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), Slot::BUFFER::LIGHTING_MODEL)); slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::BUFFER::SKINNING)); slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::BUFFER::MATERIAL)); slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::BUFFER::TEXMAPARRAY)); @@ -60,6 +61,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::MAP::METALLIC)); slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::MAP::EMISSIVE_LIGHTMAP)); slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::MAP::OCCLUSION)); + slotBindings.insert(gpu::Shader::Binding(std::string("scatteringMap"), Slot::MAP::SCATTERING)); slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT)); slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT)); slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), Slot::NORMAL_FITTING)); @@ -68,12 +70,17 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p auto locations = std::make_shared(); locations->normalFittingMapUnit = program->getTextures().findLocation("normalFittingMap"); + if (program->getTextures().findLocation("normalFittingMap") > -1) { + locations->normalFittingMapUnit = program->getTextures().findLocation("normalFittingMap"); + + } locations->albedoTextureUnit = program->getTextures().findLocation("albedoMap"); locations->roughnessTextureUnit = program->getTextures().findLocation("roughnessMap"); locations->normalTextureUnit = program->getTextures().findLocation("normalMap"); locations->metallicTextureUnit = program->getTextures().findLocation("metallicMap"); locations->emissiveTextureUnit = program->getTextures().findLocation("emissiveMap"); locations->occlusionTextureUnit = program->getTextures().findLocation("occlusionMap"); + locations->lightingModelBufferUnit = program->getBuffers().findLocation("lightingModelBuffer"); locations->skinClusterBufferUnit = program->getBuffers().findLocation("skinClusterBuffer"); locations->materialBufferUnit = program->getBuffers().findLocation("materialBuffer"); locations->texMapArrayBufferUnit = program->getBuffers().findLocation("texMapArrayBuffer"); diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index bed3bd7c68..5b60521068 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -196,10 +196,11 @@ public: class Slot { public: enum BUFFER { - SKINNING = 2, + SKINNING = 0, MATERIAL, TEXMAPARRAY, - LIGHT + LIGHTING_MODEL, + LIGHT, }; enum MAP { @@ -209,6 +210,7 @@ public: EMISSIVE_LIGHTMAP, ROUGHNESS, OCCLUSION, + SCATTERING, LIGHT_AMBIENT, NORMAL_FITTING = 10, @@ -224,6 +226,7 @@ public: int emissiveTextureUnit; int occlusionTextureUnit; int normalFittingMapUnit; + int lightingModelBufferUnit; int skinClusterBufferUnit; int materialBufferUnit; int texMapArrayBufferUnit; diff --git a/libraries/render/src/render/Task.cpp b/libraries/render/src/render/Task.cpp index 0bf20c6042..c1a917d26a 100644 --- a/libraries/render/src/render/Task.cpp +++ b/libraries/render/src/render/Task.cpp @@ -23,3 +23,4 @@ void TaskConfig::refresh() { _task->configure(*this); } + diff --git a/libraries/render/src/render/Task.h b/libraries/render/src/render/Task.h index 300c0efd56..8268090997 100644 --- a/libraries/render/src/render/Task.h +++ b/libraries/render/src/render/Task.h @@ -11,6 +11,7 @@ #ifndef hifi_render_Task_h #define hifi_render_Task_h +#include #include @@ -28,21 +29,39 @@ namespace render { +class Varying; + + // A varying piece of data, to be used as Job/Task I/O // TODO: Task IO class Varying { public: Varying() {} Varying(const Varying& var) : _concept(var._concept) {} + Varying& operator=(const Varying& var) { + _concept = var._concept; + return (*this); + } template Varying(const T& data) : _concept(std::make_shared>(data)) {} template T& edit() { return std::static_pointer_cast>(_concept)->_data; } template const T& get() const { return std::static_pointer_cast>(_concept)->_data; } + + // access potential sub varyings contained in this one. + Varying operator[] (uint8_t index) const { return (*_concept)[index]; } + uint8_t length() const { return (*_concept).length(); } + + template Varying getN (uint8_t index) const { return get()[index]; } + template Varying editN (uint8_t index) { return edit()[index]; } + protected: class Concept { public: virtual ~Concept() = default; + + virtual Varying operator[] (uint8_t index) const = 0; + virtual uint8_t length() const = 0; }; template class Model : public Concept { public: @@ -50,6 +69,12 @@ protected: Model(const Data& data) : _data(data) {} virtual ~Model() = default; + + virtual Varying operator[] (uint8_t index) const { + Varying var; + return var; + } + virtual uint8_t length() const { return 0; } Data _data; }; @@ -57,6 +82,188 @@ protected: std::shared_ptr _concept; }; +using VaryingPairBase = std::pair; +template < typename T0, typename T1 > +class VaryingSet2 : public VaryingPairBase { +public: + using Parent = VaryingPairBase; + typedef void is_proxy_tag; + + VaryingSet2() : Parent(Varying(T0()), Varying(T1())) {} + VaryingSet2(const VaryingSet2& pair) : Parent(pair.first, pair.second) {} + VaryingSet2(const Varying& first, const Varying& second) : Parent(first, second) {} + + const T0& get0() const { return first.get(); } + T0& edit0() { return first.edit(); } + + const T1& get1() const { return second.get(); } + T1& edit1() { return second.edit(); } + + virtual Varying operator[] (uint8_t index) const { + if (index == 1) { + return std::get<1>((*this)); + } else { + return std::get<0>((*this)); + } + } + virtual uint8_t length() const { return 2; } + + Varying hasVarying() const { return Varying((*this)); } +}; + + +template +class VaryingSet3 : public std::tuple{ +public: + using Parent = std::tuple; + + VaryingSet3() : Parent(Varying(T0()), Varying(T1()), Varying(T2())) {} + VaryingSet3(const VaryingSet3& src) : Parent(std::get<0>(src), std::get<1>(src), std::get<2>(src)) {} + VaryingSet3(const Varying& first, const Varying& second, const Varying& third) : Parent(first, second, third) {} + + const T0& get0() const { return std::get<0>((*this)).template get(); } + T0& edit0() { return std::get<0>((*this)).template edit(); } + + const T1& get1() const { return std::get<1>((*this)).template get(); } + T1& edit1() { return std::get<1>((*this)).template edit(); } + + const T2& get2() const { return std::get<2>((*this)).template get(); } + T2& edit2() { return std::get<2>((*this)).template edit(); } + + virtual Varying operator[] (uint8_t index) const { + if (index == 2) { + return std::get<2>((*this)); + } else if (index == 1) { + return std::get<1>((*this)); + } else { + return std::get<0>((*this)); + } + } + virtual uint8_t length() const { return 3; } + + Varying hasVarying() const { return Varying((*this)); } +}; + +template +class VaryingSet4 : public std::tuple{ +public: + using Parent = std::tuple; + + VaryingSet4() : Parent(Varying(T0()), Varying(T1()), Varying(T2()), Varying(T3())) {} + VaryingSet4(const VaryingSet4& src) : Parent(std::get<0>(src), std::get<1>(src), std::get<2>(src), std::get<3>(src)) {} + VaryingSet4(const Varying& first, const Varying& second, const Varying& third, const Varying& fourth) : Parent(first, second, third, fourth) {} + + const T0& get0() const { return std::get<0>((*this)).template get(); } + T0& edit0() { return std::get<0>((*this)).template edit(); } + + const T1& get1() const { return std::get<1>((*this)).template get(); } + T1& edit1() { return std::get<1>((*this)).template edit(); } + + const T2& get2() const { return std::get<2>((*this)).template get(); } + T2& edit2() { return std::get<2>((*this)).template edit(); } + + const T3& get3() const { return std::get<3>((*this)).template get(); } + T3& edit3() { return std::get<3>((*this)).template edit(); } + + virtual Varying operator[] (uint8_t index) const { + if (index == 3) { + return std::get<3>((*this)); + } else if (index == 2) { + return std::get<2>((*this)); + } else if (index == 1) { + return std::get<1>((*this)); + } else { + return std::get<0>((*this)); + } + } + virtual uint8_t length() const { return 4; } + + Varying hasVarying() const { return Varying((*this)); } +}; + + +template +class VaryingSet5 : public std::tuple{ +public: + using Parent = std::tuple; + + VaryingSet5() : Parent(Varying(T0()), Varying(T1()), Varying(T2()), Varying(T3()), Varying(T4())) {} + VaryingSet5(const VaryingSet5& src) : Parent(std::get<0>(src), std::get<1>(src), std::get<2>(src), std::get<3>(src), std::get<4>(src)) {} + VaryingSet5(const Varying& first, const Varying& second, const Varying& third, const Varying& fourth, const Varying& fifth) : Parent(first, second, third, fourth, fifth) {} + + const T0& get0() const { return std::get<0>((*this)).template get(); } + T0& edit0() { return std::get<0>((*this)).template edit(); } + + const T1& get1() const { return std::get<1>((*this)).template get(); } + T1& edit1() { return std::get<1>((*this)).template edit(); } + + const T2& get2() const { return std::get<2>((*this)).template get(); } + T2& edit2() { return std::get<2>((*this)).template edit(); } + + const T3& get3() const { return std::get<3>((*this)).template get(); } + T3& edit3() { return std::get<3>((*this)).template edit(); } + + const T4& get4() const { return std::get<4>((*this)).template get(); } + T4& edit4() { return std::get<4>((*this)).template edit(); } + + virtual Varying operator[] (uint8_t index) const { + if (index == 4) { + return std::get<4>((*this)); + } else if (index == 3) { + return std::get<3>((*this)); + } else if (index == 2) { + return std::get<2>((*this)); + } else if (index == 1) { + return std::get<1>((*this)); + } else { + return std::get<0>((*this)); + } + } + virtual uint8_t length() const { return 5; } + + Varying hasVarying() const { return Varying((*this)); } +}; + +template +class VaryingSet6 : public std::tuple{ +public: + using Parent = std::tuple; + + VaryingSet6() : Parent(Varying(T0()), Varying(T1()), Varying(T2()), Varying(T3()), Varying(T4()), Varying(T5())) {} + VaryingSet6(const VaryingSet6& src) : Parent(std::get<0>(src), std::get<1>(src), std::get<2>(src), std::get<3>(src), std::get<4>(src), std::get<5>(src)) {} + VaryingSet6(const Varying& first, const Varying& second, const Varying& third, const Varying& fourth, const Varying& fifth, const Varying& sixth) : Parent(first, second, third, fourth, fifth, sixth) {} + + const T0& get0() const { return std::get<0>((*this)).template get(); } + T0& edit0() { return std::get<0>((*this)).template edit(); } + + const T1& get1() const { return std::get<1>((*this)).template get(); } + T1& edit1() { return std::get<1>((*this)).template edit(); } + + const T2& get2() const { return std::get<2>((*this)).template get(); } + T2& edit2() { return std::get<2>((*this)).template edit(); } + + const T3& get3() const { return std::get<3>((*this)).template get(); } + T3& edit3() { return std::get<3>((*this)).template edit(); } + + const T4& get4() const { return std::get<4>((*this)).template get(); } + T4& edit4() { return std::get<4>((*this)).template edit(); } + + const T5& get5() const { return std::get<5>((*this)).template get(); } + T5& edit5() { return std::get<5>((*this)).template edit(); } + + Varying hasVarying() const { return Varying((*this)); } +}; + +template < class T, int NUM > +class VaryingArray : public std::array { +public: + VaryingArray() { + for (size_t i = 0; i < NUM; i++) { + (*this)[i] = Varying(T()); + } + } +}; + class Job; class Task; class JobNoIO {}; @@ -137,6 +344,7 @@ public: JobConfig(bool enabled) : alwaysEnabled{ false }, enabled{ enabled } {} bool isEnabled() { return alwaysEnabled || enabled; } + void setEnabled(bool enable) { enabled = enable; } bool alwaysEnabled{ true }; bool enabled{ true }; @@ -175,8 +383,6 @@ public: TaskConfig() = default ; TaskConfig(bool enabled) : JobConfig(enabled) {} - void init(Task* task) { _task = task; } - // getter for qml integration, prefer the templated getter Q_INVOKABLE QObject* getConfig(const QString& name) { return QObject::findChild(name); } // getter for cpp (strictly typed), prefer this getter @@ -189,6 +395,7 @@ public slots: void refresh(); private: + friend class Task; Task* _task; }; @@ -265,7 +472,7 @@ public: void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { renderContext->jobConfig = std::static_pointer_cast(_config); - if (renderContext->jobConfig->alwaysEnabled || renderContext->jobConfig->enabled) { + if (renderContext->jobConfig->alwaysEnabled || renderContext->jobConfig->isEnabled()) { jobRun(_data, sceneContext, renderContext, _input.get(), _output.edit()); } renderContext->jobConfig.reset(); @@ -305,8 +512,6 @@ public: // A task is a specialized job to run a collection of other jobs // It is defined with JobModel = Task::Model -// -// A task with a custom config *must* use the templated constructor class Task { public: using Config = TaskConfig; @@ -329,8 +534,9 @@ public: template Model(const Varying& input, A&&... args) : Concept(nullptr), _data(Data(std::forward(args)...)), _input(input), _output(Output()) { - _config = _data._config; - std::static_pointer_cast(_config)->init(&_data); + // Recreate the Config to use the templated type + _data.template createConfiguration(); + _config = _data.getConfiguration(); applyConfiguration(); } @@ -352,23 +558,19 @@ public: using Jobs = std::vector; - // A task must use its Config for construction - Task() : _config{ std::make_shared() } {} - template Task(std::shared_ptr config) : _config{ config } {} - // Create a new job in the container's queue; returns the job's output template const Varying addJob(std::string name, const Varying& input, A&&... args) { _jobs.emplace_back(name, std::make_shared(input, std::forward(args)...)); QConfigPointer config = _jobs.back().getConfiguration(); - config->setParent(_config.get()); + config->setParent(getConfiguration().get()); config->setObjectName(name.c_str()); // Connect loaded->refresh - QObject::connect(config.get(), SIGNAL(loaded()), _config.get(), SLOT(refresh())); + QObject::connect(config.get(), SIGNAL(loaded()), getConfiguration().get(), SLOT(refresh())); static const char* DIRTY_SIGNAL = "dirty()"; if (config->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { // Connect dirty->refresh if defined - QObject::connect(config.get(), SIGNAL(dirty()), _config.get(), SLOT(refresh())); + QObject::connect(config.get(), SIGNAL(dirty()), getConfiguration().get(), SLOT(refresh())); } return _jobs.back().getOutput(); @@ -378,16 +580,36 @@ public: return addJob(name, input, std::forward(args)...); } + template void createConfiguration() { + auto config = std::make_shared(); + if (_config) { + // Transfer children to the new configuration + auto children = _config->children(); + for (auto& child : children) { + child->setParent(config.get()); + QObject::connect(child, SIGNAL(loaded()), config.get(), SLOT(refresh())); + static const char* DIRTY_SIGNAL = "dirty()"; + if (child->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { + // Connect dirty->refresh if defined + QObject::connect(child, SIGNAL(dirty()), config.get(), SLOT(refresh())); + } + } + } + _config = config; + std::static_pointer_cast(_config)->_task = this; + } + std::shared_ptr getConfiguration() { - auto config = std::static_pointer_cast(_config); - // If we are here, we were not made by a Model, so we must initialize our own config - config->init(this); - return config; + if (!_config) { + createConfiguration(); + } + return std::static_pointer_cast(_config); } void configure(const QObject& configuration) { for (auto& job : _jobs) { job.applyConfiguration(); + } } @@ -400,4 +622,5 @@ protected: } + #endif // hifi_render_Task_h diff --git a/libraries/render/src/render/blurGaussianDepthAwareH.slf b/libraries/render/src/render/blurGaussianDepthAwareH.slf new file mode 100644 index 0000000000..aab1fe2b02 --- /dev/null +++ b/libraries/render/src/render/blurGaussianDepthAwareH.slf @@ -0,0 +1,22 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/7/16. +// Copyright 2016 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 BlurTask.slh@> +<$declareBlurGaussianDepthAware()$> + +in vec2 varTexCoord0; + +out vec4 outFragColor; + +void main(void) { + outFragColor = pixelShaderGaussianDepthAware(varTexCoord0, vec2(1.0, 0.0), getViewportInvWidthHeight()); +} + diff --git a/libraries/render/src/render/blurGaussianDepthAwareV.slf b/libraries/render/src/render/blurGaussianDepthAwareV.slf new file mode 100644 index 0000000000..35f0d3a381 --- /dev/null +++ b/libraries/render/src/render/blurGaussianDepthAwareV.slf @@ -0,0 +1,22 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/7/16. +// Copyright 2016 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 BlurTask.slh@> +<$declareBlurGaussianDepthAware()$> + +in vec2 varTexCoord0; + +out vec4 outFragColor; + +void main(void) { + outFragColor = pixelShaderGaussianDepthAware(varTexCoord0, vec2(0.0, 1.0), getViewportInvWidthHeight()); +} + diff --git a/libraries/render/src/render/blurGaussianH.slf b/libraries/render/src/render/blurGaussianH.slf new file mode 100644 index 0000000000..02cc73fe13 --- /dev/null +++ b/libraries/render/src/render/blurGaussianH.slf @@ -0,0 +1,23 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/7/16. +// Copyright 2016 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 BlurTask.slh@> +<$declareBlurGaussian()$> + + +in vec2 varTexCoord0; + +out vec4 outFragColor; + +void main(void) { + outFragColor = pixelShaderGaussian(varTexCoord0, vec2(1.0, 0.0), getViewportInvWidthHeight()); +} + diff --git a/libraries/render/src/render/blurGaussianV.slf b/libraries/render/src/render/blurGaussianV.slf new file mode 100644 index 0000000000..99beab6275 --- /dev/null +++ b/libraries/render/src/render/blurGaussianV.slf @@ -0,0 +1,22 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/7/16. +// Copyright 2016 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 BlurTask.slh@> +<$declareBlurGaussian()$> + + +in vec2 varTexCoord0; + +out vec4 outFragColor; + +void main(void) { + outFragColor = pixelShaderGaussian(varTexCoord0, vec2(0.0, 1.0), getViewportInvWidthHeight()); +} + diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index 0d16b26fdf..4de12af62c 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -19,7 +19,7 @@ class ScriptAudioInjector : public QObject { Q_OBJECT - Q_PROPERTY(bool isPlaying READ isPlaying) + Q_PROPERTY(bool playing READ isPlaying) Q_PROPERTY(float loudness READ getLoudness) Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions) public: diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index 3ebd3d53ce..2114289095 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "ScriptCache.h" + #include #include #include @@ -20,7 +22,7 @@ #include #include -#include "ScriptCache.h" +#include "ScriptEngines.h" #include "ScriptEngineLogging.h" ScriptCache::ScriptCache(QObject* parent) { @@ -78,22 +80,25 @@ void ScriptCache::scriptDownloaded() { QList scriptUsers = _scriptUsers.values(url); _scriptUsers.remove(url); - if (req->getResult() == ResourceRequest::Success) { - auto scriptContents = req->getData(); - _scriptCache[url] = scriptContents; - lock.unlock(); - qCDebug(scriptengine) << "Done downloading script at:" << url.toString(); + if (!DependencyManager::get()->isStopped()) { + if (req->getResult() == ResourceRequest::Success) { + auto scriptContents = req->getData(); + _scriptCache[url] = scriptContents; + lock.unlock(); + qCDebug(scriptengine) << "Done downloading script at:" << url.toString(); - foreach(ScriptUser* user, scriptUsers) { - user->scriptContentsAvailable(url, scriptContents); - } - } else { - lock.unlock(); - qCWarning(scriptengine) << "Error loading script from URL " << url; - foreach(ScriptUser* user, scriptUsers) { - user->errorInLoadingScript(url); + foreach(ScriptUser* user, scriptUsers) { + user->scriptContentsAvailable(url, scriptContents); + } + } else { + lock.unlock(); + qCWarning(scriptengine) << "Error loading script from URL " << url; + foreach(ScriptUser* user, scriptUsers) { + user->errorInLoadingScript(url); + } } } + req->deleteLater(); } @@ -162,9 +167,11 @@ void ScriptCache::scriptContentAvailable() { } } - foreach(contentAvailableCallback thisCallback, allCallbacks) { - thisCallback(url.toString(), scriptContent, true, success); - } req->deleteLater(); -} + if (!DependencyManager::get()->isStopped()) { + foreach(contentAvailableCallback thisCallback, allCallbacks) { + thisCallback(url.toString(), scriptContent, true, success); + } + } +} diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 15e896fac4..9642aaf588 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -65,7 +65,7 @@ static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3"; Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature) -static int functionSignatureMetaID = qRegisterMetaType(); +int functionSignatureMetaID = qRegisterMetaType(); static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ QString message = ""; @@ -294,13 +294,6 @@ void ScriptEngine::waitTillDoneRunning() { auto startedWaiting = usecTimestampNow(); while (workerThread->isRunning()) { - // NOTE: This will be called on the main application thread from stopAllScripts. - // The application thread will need to continue to process events, because - // the scripts will likely need to marshall messages across to the main thread, e.g. - // if they access Settings or Menu in any of their shutdown code. So: - // Process events for the main application thread, allowing invokeMethod calls to pass between threads. - QCoreApplication::processEvents(); - // If the final evaluation takes too long, then tell the script engine to stop running auto elapsedUsecs = usecTimestampNow() - startedWaiting; static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND; @@ -326,6 +319,17 @@ void ScriptEngine::waitTillDoneRunning() { } } + // NOTE: This will be called on the main application thread from stopAllScripts. + // The application thread will need to continue to process events, because + // the scripts will likely need to marshall messages across to the main thread, e.g. + // if they access Settings or Menu in any of their shutdown code. So: + // Process events for the main application thread, allowing invokeMethod calls to pass between threads. + QCoreApplication::processEvents(); + // In some cases (debugging), processEvents may give the thread enough time to shut down, so recheck it. + if (!thread()) { + break; + } + // Avoid a pure busy wait QThread::yieldCurrentThread(); } @@ -461,6 +465,7 @@ void ScriptEngine::init() { qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValueHonorReadOnly); qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue); qScriptRegisterMetaType(this, RayToEntityIntersectionResultToScriptValue, RayToEntityIntersectionResultFromScriptValue); + qScriptRegisterMetaType(this, RayToAvatarIntersectionResultToScriptValue, RayToAvatarIntersectionResultFromScriptValue); qScriptRegisterSequenceMetaType>(this); qScriptRegisterSequenceMetaType>(this); @@ -937,7 +942,13 @@ void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) { } -void ScriptEngine::stop() { +void ScriptEngine::stop(bool marshal) { + _isStopping = true; // this can be done on any thread + + if (marshal) { + QMetaObject::invokeMethod(this, "stop"); + return; + } if (!_isFinished) { _isFinished = true; emit runningStateChanged(); @@ -979,6 +990,11 @@ void ScriptEngine::updateMemoryCost(const qint64& deltaSize) { } void ScriptEngine::timerFired() { + if (DependencyManager::get()->isStopped()) { + qCDebug(scriptengine) << "Script.timerFired() while shutting down is ignored... parent script:" << getFilename(); + return; // bail early + } + QTimer* callingTimer = reinterpret_cast(sender()); CallbackData timerData = _timerFunctionMap.value(callingTimer); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 80978e4527..1077dce686 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -84,7 +84,7 @@ public: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts - Q_INVOKABLE void stop(); + Q_INVOKABLE void stop(bool marshal = false); // Stop any evaluating scripts and wait for the scripting thread to finish. void waitTillDoneRunning(); @@ -147,6 +147,9 @@ public: bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget + // this is used by code in ScriptEngines.cpp during the "reload all" operation + bool isStopping() const { return _isStopping; } + bool isDebuggable() const { return _debuggable; } void disconnectNonEssentialSignals(); @@ -189,6 +192,7 @@ protected: QString _parentURL; std::atomic _isFinished { false }; std::atomic _isRunning { false }; + std::atomic _isStopping { false }; int _evaluatesPending { 0 }; bool _isInitialized { false }; QHash _timerFunctionMap; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 70eb055d22..beddc21787 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -256,7 +256,7 @@ QVariantList ScriptEngines::getRunning() { } -static const QString SETTINGS_KEY = "Settings"; +static const QString SETTINGS_KEY = "RunningScripts"; void ScriptEngines::loadDefaultScripts() { QUrl defaultScriptsLoc = defaultScriptsLocation(); @@ -281,6 +281,43 @@ void ScriptEngines::loadScripts() { // loads all saved scripts Settings settings; + + + // START of backward compatibility code + // This following if statement is only meant to update the settings file still using the old setting key. + // If you read that comment and it has been more than a couple months since it was merged, + // then by all means, feel free to remove it. + if (!settings.childGroups().contains(SETTINGS_KEY)) { + qWarning() << "Detected old script settings config, loading from previous location"; + const QString oldKey = "Settings"; + + // Load old scripts array from settings + int size = settings.beginReadArray(oldKey); + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + QString string = settings.value("script").toString(); + if (!string.isEmpty()) { + loadScript(string); + } + } + settings.endArray(); + + // Cleanup old scripts array from settings + settings.beginWriteArray(oldKey); + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + settings.remove(""); + } + settings.endArray(); + settings.beginGroup(oldKey); + settings.remove("size"); + settings.endGroup(); + + return; + } + // END of backward compatibility code + + int size = settings.beginReadArray(SETTINGS_KEY); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); @@ -297,6 +334,7 @@ void ScriptEngines::clearScripts() { Settings settings; settings.beginWriteArray(SETTINGS_KEY); settings.remove(""); + settings.endArray(); } void ScriptEngines::saveScripts() { @@ -343,7 +381,7 @@ void ScriptEngines::stopAllScripts(bool restart) { // Stop and possibly restart all currently running scripts for (QHash::const_iterator it = _scriptEnginesHash.constBegin(); it != _scriptEnginesHash.constEnd(); it++) { - if (it.value()->isFinished()) { + if (it.value()->isFinished() || it.value()->isStopping()) { continue; } if (restart && it.value()->isUserLoaded()) { @@ -351,8 +389,7 @@ void ScriptEngines::stopAllScripts(bool restart) { reloadScript(scriptName); }); } - QMetaObject::invokeMethod(it.value(), "stop"); - //it.value()->stop(); + it.value()->stop(true); qCDebug(scriptengine) << "stopping script..." << it.key(); } } @@ -423,7 +460,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL } auto scriptEngine = getScriptEngine(scriptUrl); - if (scriptEngine) { + if (scriptEngine && !scriptEngine->isStopping()) { return scriptEngine; } diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 72bf7d529e..a9c273b2a7 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -68,6 +68,7 @@ public: // Called at shutdown time void shutdownScripting(); + bool isStopped() const { return _isStopped; } signals: void scriptCountChanged(); @@ -86,7 +87,6 @@ protected: void onScriptEngineLoaded(const QString& scriptFilename); void onScriptEngineError(const QString& scriptFilename); void launchScriptEngine(ScriptEngine* engine); - bool isStopped() const { return _isStopped; } QReadWriteLock _scriptEnginesHashLock; QHash _scriptEnginesHash; diff --git a/libraries/script-engine/src/TypedArrayPrototype.cpp b/libraries/script-engine/src/TypedArrayPrototype.cpp index bb612b393f..4de948e806 100644 --- a/libraries/script-engine/src/TypedArrayPrototype.cpp +++ b/libraries/script-engine/src/TypedArrayPrototype.cpp @@ -71,8 +71,10 @@ QScriptValue TypedArrayPrototype::subarray(qint32 begin, qint32 end) { end = (end < 0) ? length + end : end; // here we clamp the indices to fit the array + // note: begin offset is *inclusive* while end offset is *exclusive* + // (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray#Parameters) begin = glm::clamp(begin, 0, (length - 1)); - end = glm::clamp(end, 0, (length - 1)); + end = glm::clamp(end, 0, length); byteOffset += begin * bytesPerElement; length = (end - begin > 0) ? end - begin : 0; diff --git a/libraries/script-engine/src/TypedArrays.cpp b/libraries/script-engine/src/TypedArrays.cpp index c1c4117f76..4d5181ff33 100644 --- a/libraries/script-engine/src/TypedArrays.cpp +++ b/libraries/script-engine/src/TypedArrays.cpp @@ -88,7 +88,11 @@ QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engin if (arrayBuffer) { if (context->argumentCount() == 1) { // Case for entire ArrayBuffer - newObject = cls->newInstance(bufferArg, 0, arrayBuffer->size()); + if (arrayBuffer->size() % cls->_bytesPerElement != 0) { + engine->evaluate("throw \"RangeError: byteLength is not a multiple of BYTES_PER_ELEMENT\""); + } + quint32 length = arrayBuffer->size() / cls->_bytesPerElement; + newObject = cls->newInstance(bufferArg, 0, length); } else { QScriptValue byteOffsetArg = context->argument(1); if (!byteOffsetArg.isNumber()) { @@ -206,6 +210,7 @@ QScriptValue propertyHelper(const QByteArray* arrayBuffer, const QScriptString& QDataStream stream(*arrayBuffer); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); T result; stream >> result; return result; @@ -218,6 +223,7 @@ void setPropertyHelper(QByteArray* arrayBuffer, const QScriptString& name, uint if (arrayBuffer && value.isNumber()) { QDataStream stream(arrayBuffer, QIODevice::ReadWrite); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); stream << (T)value.toNumber(); } @@ -357,6 +363,7 @@ QScriptValue Float32ArrayClass::property(const QScriptValue& object, const QScri if (ok && arrayBuffer) { QDataStream stream(*arrayBuffer); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); stream.setFloatingPointPrecision(QDataStream::SinglePrecision); float result; @@ -375,6 +382,7 @@ void Float32ArrayClass::setProperty(QScriptValue& object, const QScriptString& n if (ba && value.isNumber()) { QDataStream stream(ba, QIODevice::ReadWrite); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); stream.setFloatingPointPrecision(QDataStream::SinglePrecision); stream << (float)value.toNumber(); @@ -392,6 +400,7 @@ QScriptValue Float64ArrayClass::property(const QScriptValue& object, const QScri if (ok && arrayBuffer) { QDataStream stream(*arrayBuffer); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); stream.setFloatingPointPrecision(QDataStream::DoublePrecision); double result; @@ -410,6 +419,7 @@ void Float64ArrayClass::setProperty(QScriptValue& object, const QScriptString& n if (ba && value.isNumber()) { QDataStream stream(ba, QIODevice::ReadWrite); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); stream.setFloatingPointPrecision(QDataStream::DoublePrecision); stream << (double)value.toNumber(); diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp new file mode 100644 index 0000000000..ff7ccb0164 --- /dev/null +++ b/libraries/script-engine/src/UsersScriptingInterface.cpp @@ -0,0 +1,19 @@ +// +// UsersScriptingInterface.cpp +// libraries/script-engine/src +// +// Created by Stephen Birarda on 2016-07-11. +// Copyright 2016 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 "UsersScriptingInterface.h" + +#include + +void UsersScriptingInterface::ignore(const QUuid& nodeID) { + // ask the NodeList to ignore this user (based on the session ID of their node) + DependencyManager::get()->ignoreNodeBySessionID(nodeID); +} diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h new file mode 100644 index 0000000000..0dc62c088c --- /dev/null +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -0,0 +1,28 @@ +// +// UsersScriptingInterface.h +// libraries/script-engine/src +// +// Created by Stephen Birarda on 2016-07-11. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#ifndef hifi_UsersScriptingInterface_h +#define hifi_UsersScriptingInterface_h + +#include + +class UsersScriptingInterface : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public slots: + void ignore(const QUuid& nodeID); +}; + + +#endif // hifi_UsersScriptingInterface_h diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 9a6f81b19f..15b2576331 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -140,11 +140,11 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a _async = async; if (url.toLower().left(METAVERSE_API_URL.length()) == METAVERSE_API_URL) { - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); - if (accountManager.hasValidAccessToken()) { + if (accountManager->hasValidAccessToken()) { QUrlQuery urlQuery(_url.query()); - urlQuery.addQueryItem("access_token", accountManager.getAccountInfo().getAccessToken().token); + urlQuery.addQueryItem("access_token", accountManager->getAccountInfo().getAccessToken().token); _url.setQuery(urlQuery); } diff --git a/libraries/shared/src/CPUDetect.h b/libraries/shared/src/CPUDetect.h new file mode 100644 index 0000000000..c9d2eb649b --- /dev/null +++ b/libraries/shared/src/CPUDetect.h @@ -0,0 +1,181 @@ +// +// CPUDetect.h +// libraries/shared/src +// +// Created by Ken Cooke on 6/6/16. +// Copyright 2016 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_CPUDetect_h +#define hifi_CPUDetect_h + +// +// Lightweight functions to detect SSE/AVX/AVX2 support +// + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) +#define ARCH_X86 +#endif + +#define MASK_SSE3 (1 << 0) // SSE3 +#define MASK_SSSE3 (1 << 9) // SSSE3 +#define MASK_SSE41 (1 << 19) // SSE4.1 +#define MASK_SSE42 ((1 << 20) | (1 << 23)) // SSE4.2 and POPCNT +#define MASK_AVX ((1 << 27) | (1 << 28)) // OSXSAVE and AVX +#define MASK_AVX2 (1 << 5) // AVX2 + +#if defined(ARCH_X86) && defined(_MSC_VER) + +#include + +static inline bool cpuSupportsSSE3() { + int info[4]; + + __cpuidex(info, 0x1, 0); + + return ((info[2] & MASK_SSE3) == MASK_SSE3); +} + +static inline bool cpuSupportsSSSE3() { + int info[4]; + + __cpuidex(info, 0x1, 0); + + return ((info[2] & MASK_SSSE3) == MASK_SSSE3); +} + +static inline bool cpuSupportsSSE41() { + int info[4]; + + __cpuidex(info, 0x1, 0); + + return ((info[2] & MASK_SSE41) == MASK_SSE41); +} + +static inline bool cpuSupportsSSE42() { + int info[4]; + + __cpuidex(info, 0x1, 0); + + return ((info[2] & MASK_SSE42) == MASK_SSE42); +} + +static inline bool cpuSupportsAVX() { + int info[4]; + + __cpuidex(info, 0x1, 0); + + bool result = false; + if ((info[2] & MASK_AVX) == MASK_AVX) { + + // verify OS support for YMM state + if ((_xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) { + result = true; + } + } + return result; +} + +static inline bool cpuSupportsAVX2() { + int info[4]; + + bool result = false; + if (cpuSupportsAVX()) { + + __cpuidex(info, 0x7, 0); + + if ((info[1] & MASK_AVX2) == MASK_AVX2) { + result = true; + } + } + return result; +} + +#elif defined(ARCH_X86) && defined(__GNUC__) + +#include + +static inline bool cpuSupportsSSE3() { + unsigned int eax, ebx, ecx, edx; + + return __get_cpuid(0x1, &eax, &ebx, &ecx, &edx) && ((ecx & MASK_SSE3) == MASK_SSE3); +} + +static inline bool cpuSupportsSSSE3() { + unsigned int eax, ebx, ecx, edx; + + return __get_cpuid(0x1, &eax, &ebx, &ecx, &edx) && ((ecx & MASK_SSSE3) == MASK_SSSE3); +} + +static inline bool cpuSupportsSSE41() { + unsigned int eax, ebx, ecx, edx; + + return __get_cpuid(0x1, &eax, &ebx, &ecx, &edx) && ((ecx & MASK_SSE41) == MASK_SSE41); +} + +static inline bool cpuSupportsSSE42() { + unsigned int eax, ebx, ecx, edx; + + return __get_cpuid(0x1, &eax, &ebx, &ecx, &edx) && ((ecx & MASK_SSE42) == MASK_SSE42); +} + +static inline bool cpuSupportsAVX() { + unsigned int eax, ebx, ecx, edx; + + bool result = false; + if (__get_cpuid(0x1, &eax, &ebx, &ecx, &edx) && ((ecx & MASK_AVX) == MASK_AVX)) { + + // verify OS support for YMM state + __asm__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0)); + if ((eax & 0x6) == 0x6) { + result = true; + } + } + return result; +} + +static inline bool cpuSupportsAVX2() { + unsigned int eax, ebx, ecx, edx; + + bool result = false; + if (cpuSupportsAVX()) { + + if (__get_cpuid(0x7, &eax, &ebx, &ecx, &edx) && ((ebx & MASK_AVX2) == MASK_AVX2)) { + result = true; + } + } + return result; +} + +#else + +static inline bool cpuSupportsSSE3() { + return false; +} + +static inline bool cpuSupportsSSSE3() { + return false; +} + +static inline bool cpuSupportsSSE41() { + return false; +} + +static inline bool cpuSupportsSSE42() { + return false; +} + +static inline bool cpuSupportsAVX() { + return false; +} + +static inline bool cpuSupportsAVX2() { + return false; +} + +#endif + +#endif // hifi_CPUDetect_h diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index bd8bffefd2..0c02cd5b03 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -135,6 +135,87 @@ int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatO return sizeof(quatParts); } +#define HI_BYTE(x) (uint8_t)(x >> 8) +#define LO_BYTE(x) (uint8_t)(0xff & x) + +int packOrientationQuatToSixBytes(unsigned char* buffer, const glm::quat& quatInput) { + + // find largest component + uint8_t largestComponent = 0; + for (int i = 1; i < 4; i++) { + if (fabs(quatInput[i]) > fabs(quatInput[largestComponent])) { + largestComponent = i; + } + } + + // ensure that the sign of the dropped component is always negative. + glm::quat q = quatInput[largestComponent] > 0 ? -quatInput : quatInput; + + const float MAGNITUDE = 1.0f / sqrtf(2.0f); + const uint32_t NUM_BITS_PER_COMPONENT = 15; + const uint32_t RANGE = (1 << NUM_BITS_PER_COMPONENT) - 1; + + // quantize the smallest three components into integers + uint16_t components[3]; + for (int i = 0, j = 0; i < 4; i++) { + if (i != largestComponent) { + // transform component into 0..1 range. + float value = (q[i] + MAGNITUDE) / (2.0f * MAGNITUDE); + + // quantize 0..1 into 0..range + components[j] = (uint16_t)(value * RANGE); + j++; + } + } + + // encode the largestComponent into the high bits of the first two components + components[0] = (0x7fff & components[0]) | ((0x01 & largestComponent) << 15); + components[1] = (0x7fff & components[1]) | ((0x02 & largestComponent) << 14); + + buffer[0] = HI_BYTE(components[0]); + buffer[1] = LO_BYTE(components[0]); + buffer[2] = HI_BYTE(components[1]); + buffer[3] = LO_BYTE(components[1]); + buffer[4] = HI_BYTE(components[2]); + buffer[5] = LO_BYTE(components[2]); + + return 6; +} + +int unpackOrientationQuatFromSixBytes(const unsigned char* buffer, glm::quat& quatOutput) { + + uint16_t components[3]; + components[0] = ((uint16_t)(0x7f & buffer[0]) << 8) | buffer[1]; + components[1] = ((uint16_t)(0x7f & buffer[2]) << 8) | buffer[3]; + components[2] = ((uint16_t)(0x7f & buffer[4]) << 8) | buffer[5]; + + // largestComponent is encoded into the highest bits of the first 2 components + uint8_t largestComponent = ((0x80 & buffer[2]) >> 6) | ((0x80 & buffer[0]) >> 7); + + const uint32_t NUM_BITS_PER_COMPONENT = 15; + const float RANGE = (float)((1 << NUM_BITS_PER_COMPONENT) - 1); + const float MAGNITUDE = 1.0f / sqrtf(2.0f); + float floatComponents[3]; + for (int i = 0; i < 3; i++) { + floatComponents[i] = ((float)components[i] / RANGE) * (2.0f * MAGNITUDE) - MAGNITUDE; + } + + // missingComponent is always negative. + float missingComponent = -sqrtf(1.0f - floatComponents[0] * floatComponents[0] - floatComponents[1] * floatComponents[1] - floatComponents[2] * floatComponents[2]); + + for (int i = 0, j = 0; i < 4; i++) { + if (i != largestComponent) { + quatOutput[i] = floatComponents[j]; + j++; + } else { + quatOutput[i] = missingComponent; + } + } + + return 6; +} + + // Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's // http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde, // https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java) @@ -385,6 +466,12 @@ glm::mat4 createMatFromScaleQuatAndPos(const glm::vec3& scale, const glm::quat& glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f)); } +// cancel out roll +glm::quat cancelOutRoll(const glm::quat& q) { + glm::vec3 forward = q * Vectors::FRONT; + return glm::quat_cast(glm::inverse(glm::lookAt(Vectors::ZERO, forward, Vectors::UP))); +} + // cancel out roll and pitch glm::quat cancelOutRollAndPitch(const glm::quat& q) { glm::vec3 zAxis = q * glm::vec3(0.0f, 0.0f, 1.0f); @@ -435,8 +522,8 @@ void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& seconda #ifndef NDEBUG const float MIN_LENGTH_SQUARED = 1.0e-6f; #endif - assert(fabsf(glm::length2(primaryAxis) > MIN_LENGTH_SQUARED)); - assert(fabsf(glm::length2(secondaryAxis) > MIN_LENGTH_SQUARED)); + assert(glm::length2(primaryAxis) > MIN_LENGTH_SQUARED); + assert(glm::length2(secondaryAxis) > MIN_LENGTH_SQUARED); uAxisOut = glm::normalize(primaryAxis); glm::vec3 normSecondary = glm::normalize(secondaryAxis); diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 8b1446d4e5..ef3bfeb674 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -97,6 +97,14 @@ int unpackFloatAngleFromTwoByte(const uint16_t* byteAnglePointer, float* destina int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput); int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatOutput); +// alternate compression method that picks the smallest three quaternion components. +// and packs them into 15 bits each. An additional 2 bits are used to encode which component +// was omitted. Also because the components are encoded from the -1/sqrt(2) to 1/sqrt(2) which +// gives us some extra precision over the -1 to 1 range. The final result will have a maximum +// error of +- 4.3e-5 error per compoenent. +int packOrientationQuatToSixBytes(unsigned char* buffer, const glm::quat& quatInput); +int unpackOrientationQuatFromSixBytes(const unsigned char* buffer, glm::quat& quatOutput); + // Ratios need the be highly accurate when less than 10, but not very accurate above 10, and they // are never greater than 1000 to 1, this allows us to encode each component in 16bits int packFloatRatioToTwoByte(unsigned char* buffer, float ratio); @@ -213,6 +221,7 @@ glm::detail::tvec4 lerp(const glm::detail::tvec4& x, const glm::deta glm::mat4 createMatFromQuatAndPos(const glm::quat& q, const glm::vec3& p); glm::mat4 createMatFromScaleQuatAndPos(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans); +glm::quat cancelOutRoll(const glm::quat& q); glm::quat cancelOutRollAndPitch(const glm::quat& q); glm::mat4 cancelOutRollAndPitch(const glm::mat4& m); glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& p); diff --git a/libraries/shared/src/GPUIdent.cpp b/libraries/shared/src/GPUIdent.cpp index 840dd5b402..0c4b1aaad8 100644 --- a/libraries/shared/src/GPUIdent.cpp +++ b/libraries/shared/src/GPUIdent.cpp @@ -122,7 +122,7 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) } if (count > bestCount) { bestCount = count; - _name = sString; + _name = QString(sString).trimmed(); hr = spInstance->Get(CComBSTR(_T("DriverVersion")), 0, &var, 0, 0); if (hr == S_OK) { diff --git a/libraries/shared/src/GenericQueueThread.h b/libraries/shared/src/GenericQueueThread.h index 28fcdb0ed6..7c1bdccaa7 100644 --- a/libraries/shared/src/GenericQueueThread.h +++ b/libraries/shared/src/GenericQueueThread.h @@ -12,9 +12,10 @@ #include -#include -#include -#include +#include +#include +#include +#include #include "GenericThread.h" #include "NumericalConstants.h" @@ -35,6 +36,25 @@ public: _hasItems.wakeAll(); } + void waitIdle(uint32_t maxWaitMs = UINT32_MAX) { + QElapsedTimer timer; + timer.start(); + + // FIXME this will work as long as the thread doing the wait + // is the only thread which can add work to the queue. + // It would be better if instead we had a queue empty condition to wait on + // that would ensure that we get woken as soon as we're idle the very + // first time the queue was empty. + while (timer.elapsed() < maxWaitMs) { + lock(); + if (!_items.size()) { + unlock(); + return; + } + unlock(); + } + } + protected: virtual void queueItemInternal(const T& t) { _items.push_back(t); @@ -44,6 +64,7 @@ protected: return MSECS_PER_SECOND; } + virtual bool process() { lock(); if (!_items.size()) { diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index b3920e70bc..5ae5ff740d 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -213,10 +213,12 @@ QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool if (shouldCreateIfMissing || variantMap.contains(firstKey)) { if (dotIndex == -1) { return &variantMap[firstKey]; - } else if (variantMap[firstKey].canConvert(QMetaType::QVariantMap)) { - return valueForKeyPath(*static_cast(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1), - shouldCreateIfMissing); } + if (!variantMap[firstKey].canConvert(QMetaType::QVariantMap)) { + variantMap[firstKey] = QVariantMap(); + } + return valueForKeyPath(*static_cast(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1), + shouldCreateIfMissing); } return NULL; diff --git a/libraries/shared/src/NumericalConstants.h b/libraries/shared/src/NumericalConstants.h index ca18d8ad5e..d37e1e31c5 100644 --- a/libraries/shared/src/NumericalConstants.h +++ b/libraries/shared/src/NumericalConstants.h @@ -39,6 +39,9 @@ const quint64 NSECS_PER_MSEC = 1000000; const quint64 USECS_PER_MSEC = 1000; const quint64 MSECS_PER_SECOND = 1000; const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; +const quint64 SECS_PER_MINUTE = 60; +const quint64 MINS_PER_HOUR = 60; +const quint64 SECS_PER_HOUR = SECS_PER_MINUTE * MINS_PER_HOUR; const int BITS_IN_BYTE = 8; const int BYTES_PER_KILOBYTE = 1000; diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index 1fa18903cb..1a0a19bf44 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -304,14 +304,18 @@ bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* po return true; } -unsigned char* hexStringToOctalCode(const QString& input) { +OctalCodePtr createOctalCodePtr(size_t size) { + return OctalCodePtr(new unsigned char[size], std::default_delete()); +} + +OctalCodePtr hexStringToOctalCode(const QString& input) { const int HEX_NUMBER_BASE = 16; const int HEX_BYTE_SIZE = 2; int stringIndex = 0; int byteArrayIndex = 0; // allocate byte array based on half of string length - unsigned char* bytes = new unsigned char[(input.length()) / HEX_BYTE_SIZE]; + auto bytes = createOctalCodePtr(input.length() / HEX_BYTE_SIZE); // loop through the string - 2 bytes at a time converting // it to decimal equivalent and store in byte array @@ -321,15 +325,14 @@ unsigned char* hexStringToOctalCode(const QString& input) { if (!ok) { break; } - bytes[byteArrayIndex] = (unsigned char)value; + bytes.get()[byteArrayIndex] = (unsigned char)value; stringIndex += HEX_BYTE_SIZE; byteArrayIndex++; } // something went wrong if (!ok) { - delete[] bytes; - return NULL; + return nullptr; } return bytes; } diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index 09766b685a..a0d86f32d2 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -12,9 +12,11 @@ #ifndef hifi_OctalCode_h #define hifi_OctalCode_h -#include +#include #include +#include + const int BITS_IN_OCTAL = 3; const int NUMBER_OF_COLORS = 3; // RGB! const int SIZE_OF_COLOR_DATA = NUMBER_OF_COLORS * sizeof(unsigned char); // size in bytes @@ -22,6 +24,9 @@ const int RED_INDEX = 0; const int GREEN_INDEX = 1; const int BLUE_INDEX = 2; +using OctalCodePtr = std::shared_ptr; +using OctalCodePtrList = std::vector; + void printOctalCode(const unsigned char* octalCode); size_t bytesRequiredForCodeLength(unsigned char threeBitCodes); int branchIndexWithDescendant(const unsigned char* ancestorOctalCode, const unsigned char* descendantOctalCode); @@ -57,7 +62,8 @@ typedef enum { OctalCodeComparison compareOctalCodes(const unsigned char* code1, const unsigned char* code2); +OctalCodePtr createOctalCodePtr(size_t size); QString octalCodeToHexString(const unsigned char* octalCode); -unsigned char* hexStringToOctalCode(const QString& input); +OctalCodePtr hexStringToOctalCode(const QString& input); #endif // hifi_OctalCode_h diff --git a/libraries/shared/src/Packed.h b/libraries/shared/src/Packed.h new file mode 100644 index 0000000000..3300634b96 --- /dev/null +++ b/libraries/shared/src/Packed.h @@ -0,0 +1,12 @@ +#ifndef hifi_Packed_h +#define hifi_Packed_h + +#if defined(_MSC_VER) +#define PACKED_BEGIN __pragma(pack(push, 1)) +#define PACKED_END __pragma(pack(pop)); +#else +#define PACKED_BEGIN +#define PACKED_END __attribute__((__packed__)); +#endif + +#endif diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 53fa8b30cf..8e6c1ef6ed 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -21,17 +21,17 @@ #include "RegisteredMetaTypes.h" -static int vec4MetaTypeId = qRegisterMetaType(); -static int vec3MetaTypeId = qRegisterMetaType(); -static int qVectorVec3MetaTypeId = qRegisterMetaType>(); -static int qVectorQuatMetaTypeId = qRegisterMetaType>(); -static int qVectorBoolMetaTypeId = qRegisterMetaType>(); -static int vec2MetaTypeId = qRegisterMetaType(); -static int quatMetaTypeId = qRegisterMetaType(); -static int xColorMetaTypeId = qRegisterMetaType(); -static int pickRayMetaTypeId = qRegisterMetaType(); -static int collisionMetaTypeId = qRegisterMetaType(); -static int qMapURLStringMetaTypeId = qRegisterMetaType>(); +int vec4MetaTypeId = qRegisterMetaType(); +int vec3MetaTypeId = qRegisterMetaType(); +int qVectorVec3MetaTypeId = qRegisterMetaType>(); +int qVectorQuatMetaTypeId = qRegisterMetaType>(); +int qVectorBoolMetaTypeId = qRegisterMetaType>(); +int vec2MetaTypeId = qRegisterMetaType(); +int quatMetaTypeId = qRegisterMetaType(); +int xColorMetaTypeId = qRegisterMetaType(); +int pickRayMetaTypeId = qRegisterMetaType(); +int collisionMetaTypeId = qRegisterMetaType(); +int qMapURLStringMetaTypeId = qRegisterMetaType>(); void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, mat4toScriptValue, mat4FromScriptValue); diff --git a/libraries/shared/src/SettingHandle.cpp b/libraries/shared/src/SettingHandle.cpp index d2e84d8b75..13f9ea48ce 100644 --- a/libraries/shared/src/SettingHandle.cpp +++ b/libraries/shared/src/SettingHandle.cpp @@ -10,11 +10,89 @@ // #include "SettingHandle.h" +#include "SettingManager.h" #include + + const QString Settings::firstRun { "firstRun" }; + +Settings::Settings() : + _manager(DependencyManager::get()), + _locker(&(_manager->getLock())) +{ +} + +Settings::~Settings() { + if (_prefixes.size() != 0) { + qFatal("Unstable Settings Prefixes: You must call endGroup for every beginGroup and endArray for every begin*Array call"); + } +} + +void Settings::remove(const QString& key) { + if (key == "" || _manager->contains(key)) { + _manager->remove(key); + } +} + +QStringList Settings::childGroups() const { + return _manager->childGroups(); +} + +QStringList Settings::childKeys() const { + return _manager->childKeys(); +} + +QStringList Settings::allKeys() const { + return _manager->allKeys(); +} + +bool Settings::contains(const QString& key) const { + return _manager->contains(key); +} + +int Settings::beginReadArray(const QString & prefix) { + _prefixes.push(prefix); + return _manager->beginReadArray(prefix); +} + +void Settings::beginWriteArray(const QString& prefix, int size) { + _prefixes.push(prefix); + _manager->beginWriteArray(prefix, size); +} + +void Settings::endArray() { + _prefixes.pop(); + _manager->endArray(); +} + +void Settings::setArrayIndex(int i) { + _manager->setArrayIndex(i); +} + +void Settings::beginGroup(const QString& prefix) { + _prefixes.push(prefix); + _manager->beginGroup(prefix); +} + +void Settings::endGroup() { + _prefixes.pop(); + _manager->endGroup(); +} + +void Settings::setValue(const QString& name, const QVariant& value) { + if (_manager->value(name) != value) { + _manager->setValue(name, value); + } +} + +QVariant Settings::value(const QString& name, const QVariant& defaultValue) const { + return _manager->value(name, defaultValue); +} + + void Settings::getFloatValueIfValid(const QString& name, float& floatValue) { const QVariant badDefaultValue = NAN; bool ok = true; diff --git a/libraries/shared/src/SettingHandle.h b/libraries/shared/src/SettingHandle.h index bef2daf84c..8e07d28dad 100644 --- a/libraries/shared/src/SettingHandle.h +++ b/libraries/shared/src/SettingHandle.h @@ -14,19 +14,40 @@ #include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include "SettingInterface.h" + // TODO: remove -class Settings : public QSettings { +class Settings { public: static const QString firstRun; + Settings(); + ~Settings(); + + void remove(const QString& key); + QStringList childGroups() const; + QStringList childKeys() const; + QStringList allKeys() const; + bool contains(const QString& key) const; + int beginReadArray(const QString & prefix); + void beginWriteArray(const QString& prefix, int size = -1); + void endArray(); + void setArrayIndex(int i); + + void beginGroup(const QString& prefix); + void endGroup(); + + void setValue(const QString& name, const QVariant& value); + QVariant value(const QString& name, const QVariant& defaultValue = QVariant()) const; void getFloatValueIfValid(const QString& name, float& floatValue); void getBoolValue(const QString& name, bool& boolValue); @@ -36,6 +57,11 @@ public: void setQuatValue(const QString& name, const glm::quat& quatValue); void getQuatValueIfValid(const QString& name, glm::quat& quatValue); + +private: + QSharedPointer _manager; + QWriteLocker _locker; + QStack _prefixes; }; namespace Setting { @@ -51,15 +77,40 @@ namespace Setting { virtual ~Handle() { deinit(); } // Returns setting value, returns its default value if not found - T get() { return get(_defaultValue); } + T get() const { + return get(_defaultValue); + } + // Returns setting value, returns other if not found - T get(const T& other) { maybeInit(); return (_isSet) ? _value : other; } - T getDefault() const { return _defaultValue; } + T get(const T& other) const { + maybeInit(); + return (_isSet) ? _value : other; + } + + const T& getDefault() const { + return _defaultValue; + } - void set(const T& value) { maybeInit(); _value = value; _isSet = true; } - void reset() { set(_defaultValue); } - - void remove() { maybeInit(); _isSet = false; } + void reset() { + set(_defaultValue); + } + + void set(const T& value) { + maybeInit(); + if ((!_isSet && (value != _defaultValue)) || _value != value) { + _value = value; + _isSet = true; + save(); + } + } + + void remove() { + maybeInit(); + if (_isSet) { + _isSet = false; + save(); + } + } protected: virtual void setVariant(const QVariant& variant); diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 6c5431a13e..95c6bc1efc 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -9,27 +9,33 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "SettingInterface.h" + #include #include #include #include #include "PathUtils.h" -#include "SettingInterface.h" #include "SettingManager.h" #include "SharedLogging.h" namespace Setting { - static Manager* privateInstance = nullptr; + static QSharedPointer globalManager; + const QString Interface::FIRST_RUN { "firstRun" }; + // cleans up the settings private instance. Should only be run once at closing down. void cleanupPrivateInstance() { // grab the thread before we nuke the instance - QThread* settingsManagerThread = privateInstance->thread(); - + QThread* settingsManagerThread = DependencyManager::get()->thread(); + // tell the private instance to clean itself up on its thread - privateInstance->deleteLater(); - privateInstance = NULL; + DependencyManager::destroy(); + + // + globalManager->deleteLater(); + globalManager.reset(); // quit the settings manager thread and wait on it to make sure it's gone settingsManagerThread->quit(); @@ -63,14 +69,13 @@ namespace Setting { QThread* thread = new QThread(); Q_CHECK_PTR(thread); thread->setObjectName("Settings Thread"); - - privateInstance = new Manager(); - Q_CHECK_PTR(privateInstance); - QObject::connect(privateInstance, SIGNAL(destroyed()), thread, SLOT(quit())); - QObject::connect(thread, SIGNAL(started()), privateInstance, SLOT(startTimer())); + globalManager = DependencyManager::set(); + + QObject::connect(globalManager.data(), SIGNAL(destroyed()), thread, SLOT(quit())); + QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer())); QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - privateInstance->moveToThread(thread); + globalManager->moveToThread(thread); thread->start(); qCDebug(shared) << "Settings thread started."; @@ -79,7 +84,7 @@ namespace Setting { } void Interface::init() { - if (!privateInstance) { + if (!DependencyManager::isSet()) { // WARNING: As long as we are using QSettings this should always be triggered for each Setting::Handle // in an assignment-client - the QSettings backing we use for this means persistence of these // settings from an AC (when there can be multiple terminating at same time on one machine) @@ -87,9 +92,15 @@ namespace Setting { qWarning() << "Setting::Interface::init() for key" << _key << "- Manager not yet created." << "Settings persistence disabled."; } else { - // Register Handle - privateInstance->registerHandle(this); - _isInitialized = true; + _manager = DependencyManager::get(); + auto manager = _manager.lock(); + if (manager) { + // Register Handle + manager->registerHandle(this); + _isInitialized = true; + } else { + qWarning() << "Settings interface used after manager destroyed"; + } // Load value from disk load(); @@ -97,30 +108,34 @@ namespace Setting { } void Interface::deinit() { - if (privateInstance) { - // Save value to disk - save(); - - privateInstance->removeHandle(_key); + if (_isInitialized && _manager) { + auto manager = _manager.lock(); + if (manager) { + // Save value to disk + save(); + manager->removeHandle(_key); + } } } - void Interface::maybeInit() { + void Interface::maybeInit() const { if (!_isInitialized) { - init(); + const_cast(this)->init(); } } void Interface::save() { - if (privateInstance) { - privateInstance->saveSetting(this); + auto manager = _manager.lock(); + if (manager) { + manager->saveSetting(this); } } void Interface::load() { - if (privateInstance) { - privateInstance->loadSetting(this); + auto manager = _manager.lock(); + if (manager) { + manager->loadSetting(this); } } } diff --git a/libraries/shared/src/SettingInterface.h b/libraries/shared/src/SettingInterface.h index 3aad048dbe..5e23d42223 100644 --- a/libraries/shared/src/SettingInterface.h +++ b/libraries/shared/src/SettingInterface.h @@ -12,17 +12,23 @@ #ifndef hifi_SettingInterface_h #define hifi_SettingInterface_h -#include -#include +#include +#include +#include +#include namespace Setting { + class Manager; + void preInit(); void init(); void cleanupSettings(); class Interface { public: - QString getKey() const { return _key; } + static const QString FIRST_RUN; + + const QString& getKey() const { return _key; } bool isSet() const { return _isSet; } virtual void setVariant(const QVariant& variant) = 0; @@ -33,17 +39,20 @@ namespace Setting { virtual ~Interface() = default; void init(); - void maybeInit(); + void maybeInit() const; void deinit(); void save(); void load(); - - bool _isInitialized = false; + bool _isSet = false; const QString _key; + + private: + mutable bool _isInitialized = false; friend class Manager; + mutable QWeakPointer _manager; }; } diff --git a/libraries/shared/src/SettingManager.cpp b/libraries/shared/src/SettingManager.cpp index 7c0051c809..abb8525b03 100644 --- a/libraries/shared/src/SettingManager.cpp +++ b/libraries/shared/src/SettingManager.cpp @@ -9,13 +9,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include +#include +#include +#include #include "SettingInterface.h" #include "SettingManager.h" namespace Setting { + Manager::~Manager() { // Cleanup timer stopTimer(); @@ -27,8 +29,12 @@ namespace Setting { // sync will be called in the QSettings destructor } - void Manager::registerHandle(Setting::Interface* handle) { - QString key = handle->getKey(); + // Custom deleter does nothing, because we need to shutdown later than the dependency manager + void Manager::customDeleter() { } + + + void Manager::registerHandle(Interface* handle) { + const QString& key = handle->getKey(); withWriteLock([&] { if (_handles.contains(key)) { qWarning() << "Setting::Manager::registerHandle(): Key registered more than once, overriding: " << key; @@ -44,15 +50,31 @@ namespace Setting { } void Manager::loadSetting(Interface* handle) { - handle->setVariant(value(handle->getKey())); + const auto& key = handle->getKey(); + withWriteLock([&] { + QVariant loadedValue; + if (_pendingChanges.contains(key)) { + loadedValue = _pendingChanges[key]; + } else { + loadedValue = value(key); + } + if (loadedValue.isValid()) { + handle->setVariant(loadedValue); + } + }); } + void Manager::saveSetting(Interface* handle) { + const auto& key = handle->getKey(); + QVariant handleValue = UNSET_VALUE; if (handle->isSet()) { - setValue(handle->getKey(), handle->getVariant()); - } else { - remove(handle->getKey()); + handleValue = handle->getVariant(); } + + withWriteLock([&] { + _pendingChanges[key] = handleValue; + }); } static const int SAVE_INTERVAL_MSEC = 5 * 1000; // 5 sec @@ -74,12 +96,29 @@ namespace Setting { } void Manager::saveAll() { - withReadLock([&] { - for (auto handle : _handles) { - saveSetting(handle); + bool forceSync = false; + withWriteLock([&] { + for (auto key : _pendingChanges.keys()) { + auto newValue = _pendingChanges[key]; + auto savedValue = value(key, UNSET_VALUE); + if (newValue == savedValue) { + continue; + } + if (newValue == UNSET_VALUE || !newValue.isValid()) { + forceSync = true; + remove(key); + } else { + forceSync = true; + setValue(key, newValue); + } } + _pendingChanges.clear(); }); + if (forceSync) { + sync(); + } + // Restart timer if (_saveTimer) { _saveTimer->start(); diff --git a/libraries/shared/src/SettingManager.h b/libraries/shared/src/SettingManager.h index e4981f1bce..1f309c966f 100644 --- a/libraries/shared/src/SettingManager.h +++ b/libraries/shared/src/SettingManager.h @@ -12,17 +12,23 @@ #ifndef hifi_SettingManager_h #define hifi_SettingManager_h -#include -#include -#include +#include +#include +#include +#include +#include "DependencyManager.h" #include "shared/ReadWriteLockable.h" namespace Setting { class Interface; - class Manager : public QSettings, public ReadWriteLockable { + class Manager : public QSettings, public ReadWriteLockable, public Dependency { Q_OBJECT + + public: + void customDeleter() override; + protected: ~Manager(); void registerHandle(Interface* handle); @@ -40,6 +46,8 @@ namespace Setting { private: QHash _handles; QPointer _saveTimer = nullptr; + const QVariant UNSET_VALUE { QUuid::createUuid().variant() }; + QHash _pendingChanges; friend class Interface; friend void cleanupPrivateInstance(); diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 1d0cd56b86..424c2bfa22 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -16,20 +16,23 @@ #include "NumericalConstants.h" // for MILLIMETERS_PER_METER void ShapeInfo::clear() { - _type = SHAPE_TYPE_NONE; - _halfExtents = _offset = glm::vec3(0.0f); + _url.clear(); + _pointCollection.clear(); + _triangleIndices.clear(); + _halfExtents = glm::vec3(0.0f); + _offset = glm::vec3(0.0f); _doubleHashKey.clear(); + _type = SHAPE_TYPE_NONE; } void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) { _type = type; - _points.clear(); + _halfExtents = halfExtents; switch(type) { case SHAPE_TYPE_NONE: _halfExtents = glm::vec3(0.0f); break; case SHAPE_TYPE_BOX: - _halfExtents = halfExtents; break; case SHAPE_TYPE_SPHERE: { // sphere radius is max of halfExtents @@ -38,11 +41,10 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString break; } case SHAPE_TYPE_COMPOUND: + case SHAPE_TYPE_STATIC_MESH: _url = QUrl(url); - _halfExtents = halfExtents; break; default: - _halfExtents = halfExtents; break; } _doubleHashKey.clear(); @@ -52,7 +54,6 @@ void ShapeInfo::setBox(const glm::vec3& halfExtents) { _url = ""; _type = SHAPE_TYPE_BOX; _halfExtents = halfExtents; - _points.clear(); _doubleHashKey.clear(); } @@ -60,21 +61,12 @@ void ShapeInfo::setSphere(float radius) { _url = ""; _type = SHAPE_TYPE_SPHERE; _halfExtents = glm::vec3(radius, radius, radius); - _points.clear(); _doubleHashKey.clear(); } -void ShapeInfo::setEllipsoid(const glm::vec3& halfExtents) { - _url = ""; - _type = SHAPE_TYPE_ELLIPSOID; - _halfExtents = halfExtents; - _points.clear(); - _doubleHashKey.clear(); -} - -void ShapeInfo::setConvexHulls(const QVector>& points) { - _points = points; - _type = (_points.size() > 0) ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE; +void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) { + _pointCollection = pointCollection; + _type = (_pointCollection.size() > 0) ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE; _doubleHashKey.clear(); } @@ -82,7 +74,6 @@ void ShapeInfo::setCapsuleY(float radius, float halfHeight) { _url = ""; _type = SHAPE_TYPE_CAPSULE_Y; _halfExtents = glm::vec3(radius, halfHeight, radius); - _points.clear(); _doubleHashKey.clear(); } @@ -92,13 +83,32 @@ void ShapeInfo::setOffset(const glm::vec3& offset) { } uint32_t ShapeInfo::getNumSubShapes() const { - if (_type == SHAPE_TYPE_NONE) { - return 0; - } else if (_type == SHAPE_TYPE_COMPOUND) { - return _points.size(); + switch (_type) { + case SHAPE_TYPE_NONE: + return 0; + case SHAPE_TYPE_COMPOUND: + case SHAPE_TYPE_SIMPLE_COMPOUND: + return _pointCollection.size(); + case SHAPE_TYPE_SIMPLE_HULL: + case SHAPE_TYPE_STATIC_MESH: + assert(_pointCollection.size() == 1); + // yes fall through to default + default: + return 1; } - return 1; } + +int ShapeInfo::getLargestSubshapePointCount() const { + int numPoints = 0; + for (int i = 0; i < _pointCollection.size(); ++i) { + int n = _pointCollection[i].size(); + if (n > numPoints) { + numPoints = n; + } + } + return numPoints; +} + float ShapeInfo::computeVolume() const { const float DEFAULT_VOLUME = 1.0f; float volume = DEFAULT_VOLUME; @@ -134,10 +144,6 @@ bool ShapeInfo::contains(const glm::vec3& point) const { switch(_type) { case SHAPE_TYPE_SPHERE: return glm::length(point) <= _halfExtents.x; - case SHAPE_TYPE_ELLIPSOID: { - glm::vec3 scaledPoint = glm::abs(point) / _halfExtents; - return glm::length(scaledPoint) <= 1.0f; - } case SHAPE_TYPE_CYLINDER_X: return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.z; case SHAPE_TYPE_CYLINDER_Y: @@ -182,34 +188,31 @@ const DoubleHashKey& ShapeInfo::getHash() const { // NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance. if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) { bool useOffset = glm::length2(_offset) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET; - // The key is not yet cached therefore we must compute it! To this end we bypass the const-ness - // of this method by grabbing a non-const pointer to "this" and a non-const reference to _doubleHashKey. - ShapeInfo* thisPtr = const_cast(this); - DoubleHashKey& key = thisPtr->_doubleHashKey; + // The key is not yet cached therefore we must compute it. // compute hash1 // TODO?: provide lookup table for hash/hash2 of _type rather than recompute? uint32_t primeIndex = 0; - key.computeHash((uint32_t)_type, primeIndex++); - - // compute hash1 - uint32_t hash = key.getHash(); + _doubleHashKey.computeHash((uint32_t)_type, primeIndex++); + + // compute hash1 + uint32_t hash = _doubleHashKey.getHash(); for (int j = 0; j < 3; ++j) { // NOTE: 0.49f is used to bump the float up almost half a millimeter // so the cast to int produces a round() effect rather than a floor() hash ^= DoubleHashKey::hashFunction( - (uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f), + (uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f), primeIndex++); if (useOffset) { hash ^= DoubleHashKey::hashFunction( - (uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f), + (uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f), primeIndex++); } } - key.setHash(hash); - + _doubleHashKey.setHash(hash); + // compute hash2 - hash = key.getHash2(); + hash = _doubleHashKey.getHash2(); for (int j = 0; j < 3; ++j) { // NOTE: 0.49f is used to bump the float up almost half a millimeter // so the cast to int produces a round() effect rather than a floor() @@ -226,16 +229,18 @@ const DoubleHashKey& ShapeInfo::getHash() const { hash += ~(floatHash << 10); hash = (hash << 16) | (hash >> 16); } - key.setHash2(hash); + _doubleHashKey.setHash2(hash); - QString url = _url.toString(); - if (!url.isEmpty()) { - // fold the urlHash into both parts - QByteArray baUrl = url.toLocal8Bit(); - const char *cUrl = baUrl.data(); - uint32_t urlHash = qChecksum(cUrl, baUrl.count()); - key.setHash(key.getHash() ^ urlHash); - key.setHash2(key.getHash2() ^ urlHash); + if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_STATIC_MESH) { + QString url = _url.toString(); + if (!url.isEmpty()) { + // fold the urlHash into both parts + QByteArray baUrl = url.toLocal8Bit(); + const char *cUrl = baUrl.data(); + uint32_t urlHash = qChecksum(cUrl, baUrl.count()); + _doubleHashKey.setHash(_doubleHashKey.getHash() ^ urlHash); + _doubleHashKey.setHash2(_doubleHashKey.getHash2() ^ urlHash); + } } } return _doubleHashKey; diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 79390b6680..a6ff8d6d4a 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -22,32 +22,46 @@ const float MIN_SHAPE_OFFSET = 0.001f; // offsets less than 1mm will be ignored +// Bullet has a mesh generation util for convex shapes that we used to +// trim convex hulls with many points down to only 42 points. +const int MAX_HULL_POINTS = 42; + + +const int32_t END_OF_MESH_PART = -1; // bogus vertex index at end of mesh part +const int32_t END_OF_MESH = -2; // bogus vertex index at end of mesh + enum ShapeType { SHAPE_TYPE_NONE, SHAPE_TYPE_BOX, SHAPE_TYPE_SPHERE, - SHAPE_TYPE_ELLIPSOID, - SHAPE_TYPE_PLANE, - SHAPE_TYPE_COMPOUND, SHAPE_TYPE_CAPSULE_X, SHAPE_TYPE_CAPSULE_Y, SHAPE_TYPE_CAPSULE_Z, SHAPE_TYPE_CYLINDER_X, SHAPE_TYPE_CYLINDER_Y, SHAPE_TYPE_CYLINDER_Z, - SHAPE_TYPE_LINE + SHAPE_TYPE_HULL, + SHAPE_TYPE_PLANE, + SHAPE_TYPE_COMPOUND, + SHAPE_TYPE_SIMPLE_HULL, + SHAPE_TYPE_SIMPLE_COMPOUND, + SHAPE_TYPE_STATIC_MESH }; class ShapeInfo { public: + + using PointList = QVector; + using PointCollection = QVector; + using TriangleIndices = QVector; + void clear(); void setParams(ShapeType type, const glm::vec3& halfExtents, QString url=""); void setBox(const glm::vec3& halfExtents); void setSphere(float radius); - void setEllipsoid(const glm::vec3& halfExtents); - void setConvexHulls(const QVector>& points); + void setPointCollection(const PointCollection& pointCollection); void setCapsuleY(float radius, float halfHeight); void setOffset(const glm::vec3& offset); @@ -55,12 +69,15 @@ public: const glm::vec3& getHalfExtents() const { return _halfExtents; } const glm::vec3& getOffset() const { return _offset; } - - const QVector>& getPoints() const { return _points; } uint32_t getNumSubShapes() const; - void clearPoints () { _points.clear(); } - void appendToPoints (const QVector& newPoints) { _points << newPoints; } + PointCollection& getPointCollection() { return _pointCollection; } + const PointCollection& getPointCollection() const { return _pointCollection; } + + TriangleIndices& getTriangleIndices() { return _triangleIndices; } + const TriangleIndices& getTriangleIndices() const { return _triangleIndices; } + + int getLargestSubshapePointCount() const; float computeVolume() const; @@ -71,12 +88,13 @@ public: const DoubleHashKey& getHash() const; protected: - ShapeType _type = SHAPE_TYPE_NONE; + QUrl _url; // url for model of convex collision hulls + PointCollection _pointCollection; + TriangleIndices _triangleIndices; glm::vec3 _halfExtents = glm::vec3(0.0f); glm::vec3 _offset = glm::vec3(0.0f); - DoubleHashKey _doubleHashKey; - QVector> _points; // points for convex collision hulls - QUrl _url; // url for model of convex collision hulls + mutable DoubleHashKey _doubleHashKey; + ShapeType _type = SHAPE_TYPE_NONE; }; #endif // hifi_ShapeInfo_h diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index b80fac637c..edb6fe437d 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -28,6 +28,7 @@ #ifdef Q_OS_WIN #include "CPUIdent.h" +#include #endif @@ -843,3 +844,29 @@ void printSystemInformation() { (envVariables.contains(env) ? " = " + envVariables.value(env) : " NOT FOUND"); } } + +bool getMemoryInfo(MemoryInfo& info) { +#ifdef Q_OS_WIN + MEMORYSTATUSEX ms; + ms.dwLength = sizeof(ms); + if (!GlobalMemoryStatusEx(&ms)) { + return false; + } + + info.totalMemoryBytes = ms.ullTotalPhys; + info.availMemoryBytes = ms.ullAvailPhys; + info.usedMemoryBytes = ms.ullTotalPhys - ms.ullAvailPhys; + + + PROCESS_MEMORY_COUNTERS_EX pmc; + if (!GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&pmc), sizeof(pmc))) { + return false; + } + info.processUsedMemoryBytes = pmc.PrivateUsage; + info.processPeakUsedMemoryBytes = pmc.PeakPagefileUsage; + + return true; +#endif + + return false; +} \ No newline at end of file diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 042396f474..f3e5625484 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -204,4 +204,14 @@ void disableQtBearerPoll(); void printSystemInformation(); +struct MemoryInfo { + uint64_t totalMemoryBytes; + uint64_t availMemoryBytes; + uint64_t usedMemoryBytes; + uint64_t processUsedMemoryBytes; + uint64_t processPeakUsedMemoryBytes; +}; + +bool getMemoryInfo(MemoryInfo& info); + #endif // hifi_SharedUtil_h diff --git a/libraries/shared/src/SimpleMovingAverage.h b/libraries/shared/src/SimpleMovingAverage.h index 296911ae3e..dd25705f7e 100644 --- a/libraries/shared/src/SimpleMovingAverage.h +++ b/libraries/shared/src/SimpleMovingAverage.h @@ -57,7 +57,7 @@ public: void addSample(T sample) { if (numSamples > 0) { - average = (sample * WEIGHTING) + (average * ONE_MINUS_WEIGHTING); + average = ((float)sample * WEIGHTING) + ((float)average * ONE_MINUS_WEIGHTING); } else { average = sample; } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 04bb57a688..c2563a1188 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -144,6 +144,15 @@ public: bool hasAncestorOfType(NestableType nestableType); + void getLocalTransformAndVelocities(Transform& localTransform, + glm::vec3& localVelocity, + glm::vec3& localAngularVelocity) const; + + void setLocalTransformAndVelocities( + const Transform& localTransform, + const glm::vec3& localVelocity, + const glm::vec3& localAngularVelocity); + protected: const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; @@ -151,13 +160,6 @@ protected: quint16 _parentJointIndex { 0 }; // which joint of the parent is this relative to? SpatiallyNestablePointer getParentPointer(bool& success) const; - void getLocalTransformAndVelocities(Transform& localTransform, glm::vec3& localVelocity, glm::vec3& localAngularVelocity) const; - - void setLocalTransformAndVelocities( - const Transform& localTransform, - const glm::vec3& localVelocity, - const glm::vec3& localAngularVelocity); - mutable SpatiallyNestableWeakPointer _parent; virtual void beParentOfChild(SpatiallyNestablePointer newChild) const; diff --git a/libraries/shared/src/StreamUtils.cpp b/libraries/shared/src/StreamUtils.cpp index d4378d82b3..876de2e698 100644 --- a/libraries/shared/src/StreamUtils.cpp +++ b/libraries/shared/src/StreamUtils.cpp @@ -23,7 +23,7 @@ void StreamUtil::dump(std::ostream& s, const QByteArray& buffer) { while (i < buffer.size()) { for(int j = 0; i < buffer.size() && j < row_size; ++j) { char byte = buffer[i]; - s << hex_digits[(byte >> 4) & 0x0f] << hex_digits[byte & 0x0f] << " "; + s << hex_digits[(byte >> 4) & 0x0f] << hex_digits[byte & 0x0f] << ' '; ++i; } s << "\n"; @@ -31,21 +31,21 @@ void StreamUtil::dump(std::ostream& s, const QByteArray& buffer) { } std::ostream& operator<<(std::ostream& s, const glm::vec3& v) { - s << "<" << v.x << " " << v.y << " " << v.z << ">"; + s << '(' << v.x << ' ' << v.y << ' ' << v.z << ')'; return s; } std::ostream& operator<<(std::ostream& s, const glm::quat& q) { - s << "<" << q.x << " " << q.y << " " << q.z << " " << q.w << ">"; + s << '(' << q.x << ' ' << q.y << ' ' << q.z << ' ' << q.w << ')'; return s; } std::ostream& operator<<(std::ostream& s, const glm::mat4& m) { - s << "["; + s << '['; for (int j = 0; j < 4; ++j) { - s << " " << m[0][j] << " " << m[1][j] << " " << m[2][j] << " " << m[3][j] << ";"; + s << ' ' << m[0][j] << ' ' << m[1][j] << ' ' << m[2][j] << ' ' << m[3][j] << ';'; } - s << " ]"; + s << " ]"; return s; } @@ -69,54 +69,37 @@ QDataStream& operator>>(QDataStream& in, glm::quat& quaternion) { #include QDebug& operator<<(QDebug& dbg, const glm::vec2& v) { - dbg.nospace() << "{type='glm::vec2'" - ", x=" << v.x << - ", y=" << v.y << - "}"; + dbg.nospace() << '(' << v.x << ", " << v.y << ')'; return dbg; } QDebug& operator<<(QDebug& dbg, const glm::vec3& v) { - dbg.nospace() << "{type='glm::vec3'" - ", x=" << v.x << - ", y=" << v.y << - ", z=" << v.z << - "}"; + dbg.nospace() << '(' << v.x << ", " << v.y << ", " << v.z << ')'; return dbg; } QDebug& operator<<(QDebug& dbg, const glm::vec4& v) { - dbg.nospace() << "{type='glm::vec4'" - ", x=" << v.x << - ", y=" << v.y << - ", z=" << v.z << - ", w=" << v.w << - "}"; + dbg.nospace() << '(' << v.x << ", " << v.y << ", " << v.z << ", " << v.w << ')'; return dbg; } QDebug& operator<<(QDebug& dbg, const glm::quat& q) { - dbg.nospace() << "{type='glm::quat'" - ", x=" << q.x << - ", y=" << q.y << - ", z=" << q.z << - ", w=" << q.w << - "}"; + dbg.nospace() << '(' << q.x << ", " << q.y << ", " << q.z << ", " << q.w << ')'; return dbg; } QDebug& operator<<(QDebug& dbg, const glm::mat4& m) { - dbg.nospace() << "{type='glm::mat4', ["; + dbg.nospace() << '['; for (int j = 0; j < 4; ++j) { dbg << ' ' << m[0][j] << ' ' << m[1][j] << ' ' << m[2][j] << ' ' << m[3][j] << ';'; } - return dbg << " ]}"; + return dbg << " ]"; } QDebug& operator<<(QDebug& dbg, const QVariantHash& v) { - dbg.nospace() << "["; + dbg.nospace() << "[ "; for (QVariantHash::const_iterator it = v.constBegin(); it != v.constEnd(); it++) { - dbg << it.key() << ":" << it.value(); + dbg << it.key() << ':' << it.value(); } return dbg << " ]"; } diff --git a/libraries/shared/src/shared/Bilateral.h b/libraries/shared/src/shared/Bilateral.h new file mode 100644 index 0000000000..c4daf60177 --- /dev/null +++ b/libraries/shared/src/shared/Bilateral.h @@ -0,0 +1,49 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 +// + +#pragma once + +namespace bilateral { + enum class Side { + Left = 0, + Right = 1 + }; + + using Indices = Side; + + enum class Bits { + Left = 0x01, + Right = 0x02 + }; + + inline uint8_t bit(Side side) { + switch (side) { + case Side::Left: + return 0x01; + case Side::Right: + return 0x02; + } + return UINT8_MAX; + } + + inline uint8_t index(Side side) { + switch (side) { + case Side::Left: + return 0; + case Side::Right: + return 1; + } + return UINT8_MAX; + } + + template + void for_each_side(F f) { + f(Side::Left); + f(Side::Right); + } +} diff --git a/libraries/shared/src/shared/ReadWriteLockable.h b/libraries/shared/src/shared/ReadWriteLockable.h index 07b46bb92a..539678a73d 100644 --- a/libraries/shared/src/shared/ReadWriteLockable.h +++ b/libraries/shared/src/shared/ReadWriteLockable.h @@ -45,6 +45,8 @@ public: template bool withTryReadLock(F&& f, int timeout) const; + QReadWriteLock& getLock() const { return _lock; } + private: mutable QReadWriteLock _lock { QReadWriteLock::Recursive }; }; diff --git a/libraries/shared/src/shared/Shapes.cpp b/libraries/shared/src/shared/Shapes.cpp new file mode 100644 index 0000000000..dabdf21202 --- /dev/null +++ b/libraries/shared/src/shared/Shapes.cpp @@ -0,0 +1,186 @@ +// +// Created by Bradley Austin Davis on 2016/05/26 +// Copyright 2013-2016 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 "Shapes.h" + +namespace geometry { + +using glm::vec3; + +// The golden ratio +static const float PHI = 1.61803398874f; + +Solid<3> tesselate(const Solid<3>& solid_, int count) { + Solid<3> solid = solid_; + float length = glm::length(solid.vertices[0]); + for (int i = 0; i < count; ++i) { + Solid<3> result { solid.vertices, {} }; + result.vertices.reserve(solid.vertices.size() + solid.faces.size() * 3); + for (size_t f = 0; f < solid.faces.size(); ++f) { + Index baseVertex = (Index)result.vertices.size(); + const Face<3>& oldFace = solid.faces[f]; + const vec3& a = solid.vertices[oldFace[0]]; + const vec3& b = solid.vertices[oldFace[1]]; + const vec3& c = solid.vertices[oldFace[2]]; + vec3 ab = glm::normalize(a + b) * length; + vec3 bc = glm::normalize(b + c) * length; + vec3 ca = glm::normalize(c + a) * length; + result.vertices.push_back(ab); + result.vertices.push_back(bc); + result.vertices.push_back(ca); + result.faces.push_back(Face<3>{ { oldFace[0], baseVertex, baseVertex + 2 } }); + result.faces.push_back(Face<3>{ { baseVertex, oldFace[1], baseVertex + 1 } }); + result.faces.push_back(Face<3>{ { baseVertex + 1, oldFace[2], baseVertex + 2 } }); + result.faces.push_back(Face<3>{ { baseVertex, baseVertex + 1, baseVertex + 2 } }); + } + solid = result; + } + return solid; +} + +const Solid<3>& tetrahedron() { + static const auto A = vec3(1, 1, 1); + static const auto B = vec3(1, -1, -1); + static const auto C = vec3(-1, 1, -1); + static const auto D = vec3(-1, -1, 1); + static const Solid<3> TETRAHEDRON = Solid<3>{ + { A, B, C, D }, + FaceVector<3>{ + Face<3> { { 0, 1, 2 } }, + Face<3> { { 3, 1, 0 } }, + Face<3> { { 2, 3, 0 } }, + Face<3> { { 2, 1, 3 } }, + } + }.fitDimension(0.5f); + return TETRAHEDRON; +} + +const Solid<4>& cube() { + static const auto A = vec3(1, 1, 1); + static const auto B = vec3(-1, 1, 1); + static const auto C = vec3(-1, 1, -1); + static const auto D = vec3(1, 1, -1); + static const Solid<4> CUBE = Solid<4>{ + { A, B, C, D, -A, -B, -C, -D }, + FaceVector<4>{ + Face<4> { { 3, 2, 1, 0 } }, + Face<4> { { 0, 1, 7, 6 } }, + Face<4> { { 1, 2, 4, 7 } }, + Face<4> { { 2, 3, 5, 4 } }, + Face<4> { { 3, 0, 6, 5 } }, + Face<4> { { 4, 5, 6, 7 } }, + } + }.fitDimension(0.5f); + return CUBE; +} + +const Solid<3>& octahedron() { + static const auto A = vec3(0, 1, 0); + static const auto B = vec3(0, -1, 0); + static const auto C = vec3(0, 0, 1); + static const auto D = vec3(0, 0, -1); + static const auto E = vec3(1, 0, 0); + static const auto F = vec3(-1, 0, 0); + static const Solid<3> OCTAHEDRON = Solid<3>{ + { A, B, C, D, E, F}, + FaceVector<3> { + Face<3> { { 0, 2, 4, } }, + Face<3> { { 0, 4, 3, } }, + Face<3> { { 0, 3, 5, } }, + Face<3> { { 0, 5, 2, } }, + Face<3> { { 1, 4, 2, } }, + Face<3> { { 1, 3, 4, } }, + Face<3> { { 1, 5, 3, } }, + Face<3> { { 1, 2, 5, } }, + } + }.fitDimension(0.5f); + return OCTAHEDRON; +} + +const Solid<5>& dodecahedron() { + static const float P = PHI; + static const float IP = 1.0f / PHI; + static const vec3 A = vec3(IP, P, 0); + static const vec3 B = vec3(-IP, P, 0); + static const vec3 C = vec3(-1, 1, 1); + static const vec3 D = vec3(0, IP, P); + static const vec3 E = vec3(1, 1, 1); + static const vec3 F = vec3(1, 1, -1); + static const vec3 G = vec3(-1, 1, -1); + static const vec3 H = vec3(-P, 0, IP); + static const vec3 I = vec3(0, -IP, P); + static const vec3 J = vec3(P, 0, IP); + + static const Solid<5> DODECAHEDRON = Solid<5>{ + { + A, B, C, D, E, F, G, H, I, J, + -A, -B, -C, -D, -E, -F, -G, -H, -I, -J, + }, + FaceVector<5> { + Face<5> { { 0, 1, 2, 3, 4 } }, + Face<5> { { 0, 5, 18, 6, 1 } }, + Face<5> { { 1, 6, 19, 7, 2 } }, + Face<5> { { 2, 7, 15, 8, 3 } }, + Face<5> { { 3, 8, 16, 9, 4 } }, + Face<5> { { 4, 9, 17, 5, 0 } }, + Face<5> { { 14, 13, 12, 11, 10 } }, + Face<5> { { 11, 16, 8, 15, 10 } }, + Face<5> { { 12, 17, 9, 16, 11 } }, + Face<5> { { 13, 18, 5, 17, 12 } }, + Face<5> { { 14, 19, 6, 18, 13 } }, + Face<5> { { 10, 15, 7, 19, 14 } }, + } + }.fitDimension(0.5f); + return DODECAHEDRON; +} + +const Solid<3>& icosahedron() { + static const float N = 1.0f / PHI; + static const float P = 1.0f; + static const auto A = vec3(N, P, 0); + static const auto B = vec3(-N, P, 0); + static const auto C = vec3(0, N, P); + static const auto D = vec3(P, 0, N); + static const auto E = vec3(P, 0, -N); + static const auto F = vec3(0, N, -P); + + static const Solid<3> ICOSAHEDRON = Solid<3> { + { + A, B, C, D, E, F, + -A, -B, -C, -D, -E, -F, + }, + FaceVector<3> { + Face<3> { { 1, 2, 0 } }, + Face<3> { { 2, 3, 0 } }, + Face<3> { { 3, 4, 0 } }, + Face<3> { { 4, 5, 0 } }, + Face<3> { { 5, 1, 0 } }, + + Face<3> { { 1, 10, 2 } }, + Face<3> { { 11, 2, 10 } }, + Face<3> { { 2, 11, 3 } }, + Face<3> { { 7, 3, 11 } }, + Face<3> { { 3, 7, 4 } }, + Face<3> { { 8, 4, 7 } }, + Face<3> { { 4, 8, 5 } }, + Face<3> { { 9, 5, 8 } }, + Face<3> { { 5, 9, 1 } }, + Face<3> { { 10, 1, 9 } }, + + Face<3> { { 8, 7, 6 } }, + Face<3> { { 9, 8, 6 } }, + Face<3> { { 10, 9, 6 } }, + Face<3> { { 11, 10, 6 } }, + Face<3> { { 7, 11, 6 } }, + } + }.fitDimension(0.5f); + return ICOSAHEDRON; +} + +} + diff --git a/libraries/shared/src/shared/Shapes.h b/libraries/shared/src/shared/Shapes.h new file mode 100644 index 0000000000..3486a0a663 --- /dev/null +++ b/libraries/shared/src/shared/Shapes.h @@ -0,0 +1,79 @@ +// +// Created by Bradley Austin Davis on 2016/05/26 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_shared_shapes +#define hifi_shared_shapes + +#include + +#include +#include + +#include + +namespace geometry { + + using Index = uint32_t; + using Vec = glm::vec3; + using VertexVector = std::vector; + using IndexVector = std::vector; + + template + using Face = std::array; + + template + using FaceVector = std::vector>; + + template + struct Solid { + VertexVector vertices; + FaceVector faces; + + Solid& fitDimension(float newMaxDimension) { + float maxDimension = 0; + for (const auto& vertex : vertices) { + maxDimension = std::max(maxDimension, std::max(std::max(vertex.x, vertex.y), vertex.z)); + } + float multiplier = newMaxDimension / maxDimension; + for (auto& vertex : vertices) { + vertex *= multiplier; + } + return *this; + } + + Vec getFaceNormal(size_t faceIndex) const { + Vec result; + const auto& face = faces[faceIndex]; + for (size_t i = 0; i < N; ++i) { + result += vertices[face[i]]; + } + result /= N; + return glm::normalize(result); + } + }; + + template + size_t triangulatedFaceTriangleCount() { + return N - 2; + } + + template + size_t triangulatedFaceIndexCount() { + return triangulatedFaceTriangleCount() * 3; + } + + Solid<3> tesselate(const Solid<3>& solid, int count); + const Solid<3>& tetrahedron(); + const Solid<4>& cube(); + const Solid<3>& octahedron(); + const Solid<5>& dodecahedron(); + const Solid<3>& icosahedron(); +} + +#endif diff --git a/libraries/steamworks-wrapper/CMakeLists.txt b/libraries/steamworks-wrapper/CMakeLists.txt new file mode 100644 index 0000000000..0cbe3bb5ad --- /dev/null +++ b/libraries/steamworks-wrapper/CMakeLists.txt @@ -0,0 +1,5 @@ +set(TARGET_NAME steamworks-wrapper) +setup_hifi_library() +link_hifi_libraries() + +target_steamworks() \ No newline at end of file diff --git a/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.cpp b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.cpp new file mode 100644 index 0000000000..0f06e03672 --- /dev/null +++ b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.cpp @@ -0,0 +1,19 @@ +// +// SteamClient.cpp +// steamworks-wrapper/src/steamworks-wrapper +// +// Created by Clement Brisset on 6/8/16. +// Copyright 2016 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 "SteamClient.h" + +#include + + + + + diff --git a/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.h b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.h new file mode 100644 index 0000000000..369641b0c7 --- /dev/null +++ b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.h @@ -0,0 +1,21 @@ +// +// SteamClient.h +// steamworks-wrapper/src/steamworks-wrapper +// +// Created by Clement Brisset on 6/8/16. +// Copyright 2016 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_SteamClient_h +#define hifi_SteamClient_h + +class SteamClient { + + +}; + +#endif // hifi_SteamClient_h \ No newline at end of file diff --git a/libraries/ui-plugins/CMakeLists.txt b/libraries/ui-plugins/CMakeLists.txt new file mode 100644 index 0000000000..9ce189b117 --- /dev/null +++ b/libraries/ui-plugins/CMakeLists.txt @@ -0,0 +1,3 @@ +set(TARGET_NAME ui-plugins) +setup_hifi_library(OpenGL) +link_hifi_libraries(shared plugins ui) diff --git a/libraries/plugins/src/plugins/PluginContainer.cpp b/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp similarity index 100% rename from libraries/plugins/src/plugins/PluginContainer.cpp rename to libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/ui-plugins/src/ui-plugins/PluginContainer.h similarity index 94% rename from libraries/plugins/src/plugins/PluginContainer.h rename to libraries/ui-plugins/src/ui-plugins/PluginContainer.h index e1d1a212e2..74ac834057 100644 --- a/libraries/plugins/src/plugins/PluginContainer.h +++ b/libraries/ui-plugins/src/ui-plugins/PluginContainer.h @@ -16,7 +16,7 @@ #include #include -#include "Forward.h" +#include class QAction; class GLWidget; @@ -63,8 +63,8 @@ public: virtual GLWidget* getPrimaryWidget() = 0; virtual MainWindow* getPrimaryWindow() = 0; virtual QOpenGLContext* getPrimaryContext() = 0; - virtual bool isForeground() = 0; - virtual const DisplayPluginPointer getActiveDisplayPlugin() const = 0; + virtual bool isForeground() const = 0; + virtual DisplayPluginPointer getActiveDisplayPlugin() const = 0; /// settings interface bool getBoolSetting(const QString& settingName, bool defaultValue); @@ -84,3 +84,4 @@ protected: std::map _exclusiveGroups; QRect _savedGeometry { 10, 120, 800, 600 }; }; + diff --git a/libraries/ui/src/ErrorDialog.cpp b/libraries/ui/src/ErrorDialog.cpp index ab36ef8d36..fcd73b4cc0 100644 --- a/libraries/ui/src/ErrorDialog.cpp +++ b/libraries/ui/src/ErrorDialog.cpp @@ -22,10 +22,6 @@ QString ErrorDialog::text() const { return _text; } -void ErrorDialog::setVisible(bool v) { - OffscreenQmlDialog::setVisible(v); -} - void ErrorDialog::setText(const QString& arg) { if (arg != _text) { _text = arg; diff --git a/libraries/ui/src/ErrorDialog.h b/libraries/ui/src/ErrorDialog.h index 665090da1a..38954714a7 100644 --- a/libraries/ui/src/ErrorDialog.h +++ b/libraries/ui/src/ErrorDialog.h @@ -30,7 +30,6 @@ public: QString text() const; public slots: - virtual void setVisible(bool v); void setText(const QString& arg); signals: diff --git a/libraries/ui/src/OffscreenQmlDialog.cpp b/libraries/ui/src/OffscreenQmlDialog.cpp index 43514c4761..2d1ca20876 100644 --- a/libraries/ui/src/OffscreenQmlDialog.cpp +++ b/libraries/ui/src/OffscreenQmlDialog.cpp @@ -17,7 +17,7 @@ OffscreenQmlDialog::~OffscreenQmlDialog() { } void OffscreenQmlDialog::hide() { - static_cast(parent())->setVisible(false); + parent()->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, false); } QString OffscreenQmlDialog::title() const { diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 0c1ad69d72..28aba4a365 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -29,6 +29,9 @@ class OffscreenFlags : public QObject { Q_OBJECT Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged) + // Allow scripts that are doing their own navigation support to disable navigation focus (i.e. handControllerPointer.js) + Q_PROPERTY(bool navigationFocusDisabled READ isNavigationFocusDisabled WRITE setNavigationFocusDisabled NOTIFY navigationFocusDisabledChanged) + public: OffscreenFlags(QObject* parent = nullptr) : QObject(parent) {} @@ -40,11 +43,21 @@ public: } } + bool isNavigationFocusDisabled() const { return _navigationFocusDisabled; } + void setNavigationFocusDisabled(bool disabled) { + if (_navigationFocusDisabled != disabled) { + _navigationFocusDisabled = disabled; + emit navigationFocusDisabledChanged(); + } + } + signals: void navigationFocusedChanged(); + void navigationFocusDisabledChanged(); private: bool _navigationFocused { false }; + bool _navigationFocusDisabled{ false }; }; QString fixupHifiUrl(const QString& urlString) { @@ -53,8 +66,8 @@ QString fixupHifiUrl(const QString& urlString) { QUrl url(urlString); QUrlQuery query(url); if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) { - AccountManager& accountManager = AccountManager::getInstance(); - query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager.getAccountInfo().getAccessToken().token); + auto accountManager = DependencyManager::get(); + query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token); url.setQuery(query.query()); return url.toString(); } @@ -103,6 +116,10 @@ bool OffscreenUi::shouldSwallowShortcut(QEvent* event) { OffscreenUi::OffscreenUi() { } +QObject* OffscreenUi::getFlags() { + return offscreenFlags; +} + void OffscreenUi::create(QOpenGLContext* context) { OffscreenQmlSurface::create(context); auto rootContext = getRootContext(); @@ -121,32 +138,28 @@ void OffscreenUi::show(const QUrl& url, const QString& name, std::functionfindChild(name); } + if (item) { - item->setVisible(true); + QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).write(true); } } void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function f) { QQuickItem* item = getRootItem()->findChild(name); - // Already loaded? - if (item) { - emit showDesktop(); - item->setVisible(!item->isVisible()); + if (!item) { + show(url, name, f); return; } - load(url, f); - item = getRootItem()->findChild(name); - if (item && !item->isVisible()) { - emit showDesktop(); - item->setVisible(true); - } + // Already loaded, so just flip the bit + QQmlProperty shownProperty(item, OFFSCREEN_VISIBILITY_PROPERTY); + shownProperty.write(!shownProperty.read().toBool()); } void OffscreenUi::hide(const QString& name) { QQuickItem* item = getRootItem()->findChild(name); if (item) { - item->setVisible(false); + QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).write(false); } } @@ -345,6 +358,20 @@ QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const Q return waitForInputDialogResult(createInputDialog(icon, title, label, current)); } +void OffscreenUi::togglePinned() { + bool invokeResult = QMetaObject::invokeMethod(_desktop, "togglePinned"); + if (!invokeResult) { + qWarning() << "Failed to toggle window visibility"; + } +} + +void OffscreenUi::setPinned(bool pinned) { + bool invokeResult = QMetaObject::invokeMethod(_desktop, "setPinned", Q_ARG(QVariant, pinned)); + if (!invokeResult) { + qWarning() << "Failed to set window visibility"; + } +} + void OffscreenUi::addMenuInitializer(std::function f) { if (!_vrMenu) { _queuedMenuInitializers.push_back(f); @@ -382,7 +409,7 @@ QVariant OffscreenUi::waitForInputDialogResult(QQuickItem* inputDialog) { } bool OffscreenUi::navigationFocused() { - return offscreenFlags->isNavigationFocused(); + return !offscreenFlags->isNavigationFocusDisabled() && offscreenFlags->isNavigationFocused(); } void OffscreenUi::setNavigationFocused(bool focused) { @@ -482,10 +509,9 @@ void OffscreenUi::unfocusWindows() { Q_ASSERT(invokeResult); } -void OffscreenUi::toggleMenu(const QPoint& screenPosition) { +void OffscreenUi::toggleMenu(const QPoint& screenPosition) { // caller should already have mapped using getReticlePosition emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works - auto virtualPos = mapToVirtualScreen(screenPosition, nullptr); - QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, virtualPos)); + QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, screenPosition)); } @@ -590,6 +616,7 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { // let the parent class do it's work bool result = OffscreenQmlSurface::eventFilter(originalDestination, event); + // Check if this is a key press/release event that might need special attention auto type = event->type(); if (type != QEvent::KeyPress && type != QEvent::KeyRelease) { @@ -597,7 +624,8 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { } QKeyEvent* keyEvent = dynamic_cast(event); - bool& pressed = _pressedKeys[keyEvent->key()]; + auto key = keyEvent->key(); + bool& pressed = _pressedKeys[key]; // Keep track of which key press events the QML has accepted if (result && QEvent::KeyPress == type) { @@ -607,7 +635,7 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { // QML input elements absorb key press, but apparently not key release. // therefore we want to ensure that key release events for key presses that were // accepted by the QML layer are suppressed - if (!result && type == QEvent::KeyRelease && pressed) { + if (type == QEvent::KeyRelease && pressed) { pressed = false; return true; } diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 5a16b49491..2bd00bf612 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -28,6 +28,8 @@ class VrMenu; +#define OFFSCREEN_VISIBILITY_PROPERTY "shown" + class OffscreenUi : public OffscreenQmlSurface, public Dependency { Q_OBJECT @@ -44,9 +46,16 @@ public: void setNavigationFocused(bool focused); void unfocusWindows(); void toggleMenu(const QPoint& screenCoordinates); + + + // Setting pinned to true will hide all overlay elements on the desktop that don't have a pinned flag + void setPinned(bool pinned = true); + + void togglePinned(); + bool eventFilter(QObject* originalDestination, QEvent* event) override; void addMenuInitializer(std::function f); - + QObject* getFlags(); QQuickItem* getDesktop(); QQuickItem* getToolWindow(); diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 396d716cda..7d56e51495 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -118,11 +118,12 @@ void QmlWindowClass::initQml(QVariantMap properties) { } bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool(); - object->setProperty(VISIBILE_PROPERTY, visible); + object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible); object->setProperty(SOURCE_PROPERTY, _source); // Forward messages received from QML on to the script connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection); + connect(_qmlWindow, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection); }); } Q_ASSERT(_qmlWindow); @@ -163,8 +164,7 @@ void QmlWindowClass::setVisible(bool visible) { QMetaObject::invokeMethod(targetWindow, "showTabForUrl", Qt::QueuedConnection, Q_ARG(QVariant, _source), Q_ARG(QVariant, visible)); } else { DependencyManager::get()->executeOnUiThread([=] { - targetWindow->setVisible(visible); - //emit visibilityChanged(visible); + targetWindow->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible); }); } } diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index e1865b133a..c30027df6e 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -24,7 +24,7 @@ class QmlWindowClass : public QObject { Q_OBJECT Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition NOTIFY positionChanged) Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged) - Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) public: static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); @@ -52,7 +52,7 @@ public slots: void sendToQml(const QVariant& message); signals: - void visibilityChanged(bool visible); // Tool window + void visibleChanged(); void positionChanged(); void sizeChanged(); void moved(glm::vec2 position); diff --git a/libraries/ui/src/Tooltip.cpp b/libraries/ui/src/Tooltip.cpp index 56ba8a95b9..94e04f34b6 100644 --- a/libraries/ui/src/Tooltip.cpp +++ b/libraries/ui/src/Tooltip.cpp @@ -47,10 +47,6 @@ void Tooltip::setImageURL(const QString& imageURL) { } } -void Tooltip::setVisible(bool visible) { - QQuickItem::setVisible(visible); -} - QString Tooltip::showTip(const QString& title, const QString& description) { const QString newTipId = QUuid().createUuid().toString(); @@ -84,16 +80,16 @@ void Tooltip::requestHyperlinkImage() { // should the network link be removed from UI at a later date. // we possibly have a valid place name - so ask the API for the associated info - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; callbackParams.jsonCallbackMethod = "handleAPIResponse"; - accountManager.sendRequest(GET_PLACE.arg(_title), - AccountManagerAuth::None, - QNetworkAccessManager::GetOperation, - callbackParams); + accountManager->sendRequest(GET_PLACE.arg(_title), + AccountManagerAuth::None, + QNetworkAccessManager::GetOperation, + callbackParams); } } } diff --git a/libraries/ui/src/Tooltip.h b/libraries/ui/src/Tooltip.h index d1c7330a74..5e884a7aea 100644 --- a/libraries/ui/src/Tooltip.h +++ b/libraries/ui/src/Tooltip.h @@ -39,8 +39,6 @@ public: static void closeTip(const QString& tipId); public slots: - virtual void setVisible(bool v); - void setTitle(const QString& title); void setDescription(const QString& description); void setImageURL(const QString& imageURL); diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 2d6113ad63..c2732197d3 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -234,7 +234,5 @@ void VrMenu::removeAction(QAction* action) { QObject* menu = item->parent(); // Proxy QuickItem requests through the QML layer QQuickMenuBase* qmlItem = reinterpret_cast(item); - bool invokeResult = QMetaObject::invokeMethod(menu, "removeItem", Qt::DirectConnection, - Q_ARG(QQuickMenuBase*, qmlItem)); - Q_ASSERT(invokeResult); + QMetaObject::invokeMethod(menu, "removeItem", Qt::DirectConnection, Q_ARG(QQuickMenuBase*, qmlItem)); } diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 55b18b122c..1e2a672107 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -10,9 +10,26 @@ file(GLOB PLUGIN_SUBDIRS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/*") list(REMOVE_ITEM PLUGIN_SUBDIRS "CMakeFiles") -foreach(DIR ${PLUGIN_SUBDIRS}) - if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${DIR}") - add_subdirectory(${DIR}) - endif() -endforeach() +# client-side plugins +if (NOT SERVER_ONLY) + set(DIR "oculus") + add_subdirectory(${DIR}) + set(DIR "hifiSdl2") + add_subdirectory(${DIR}) + set(DIR "openvr") + add_subdirectory(${DIR}) + set(DIR "oculusLegacy") + add_subdirectory(${DIR}) + set(DIR "hifiSixense") + add_subdirectory(${DIR}) + set(DIR "hifiSpacemouse") + add_subdirectory(${DIR}) + set(DIR "hifiNeuron") + add_subdirectory(${DIR}) +endif() +# server-side plugins +set(DIR "pcmCodec") +add_subdirectory(${DIR}) +set(DIR "hifiCodec") +add_subdirectory(${DIR}) diff --git a/plugins/hifiCodec/CMakeLists.txt b/plugins/hifiCodec/CMakeLists.txt new file mode 100644 index 0000000000..b278e839e4 --- /dev/null +++ b/plugins/hifiCodec/CMakeLists.txt @@ -0,0 +1,16 @@ +# +# Created by Brad Hefta-Gaub on 7/10/2016 +# Copyright 2016 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 +# + +set(TARGET_NAME hifiCodec) +setup_hifi_client_server_plugin() +link_hifi_libraries(audio shared plugins) +add_dependency_external_projects(hifiAudioCodec) +target_include_directories(${TARGET_NAME} PRIVATE ${HIFIAUDIOCODEC_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${HIFIAUDIOCODEC_LIBRARIES}) +install_beside_console() + diff --git a/plugins/hifiCodec/src/HiFiCodec.cpp b/plugins/hifiCodec/src/HiFiCodec.cpp new file mode 100644 index 0000000000..4e9336ff90 --- /dev/null +++ b/plugins/hifiCodec/src/HiFiCodec.cpp @@ -0,0 +1,93 @@ +// +// HiFiCodec.cpp +// plugins/hifiCodec/src +// +// Created by Brad Hefta-Gaub on 7/10/2016 +// Copyright 2016 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 "HiFiCodec.h" + +const QString HiFiCodec::NAME = "hifiAC"; + +void HiFiCodec::init() { +} + +void HiFiCodec::deinit() { +} + +bool HiFiCodec::activate() { + CodecPlugin::activate(); + return true; +} + +void HiFiCodec::deactivate() { + CodecPlugin::deactivate(); +} + + +bool HiFiCodec::isSupported() const { + return true; +} + +class HiFiEncoder : public Encoder, public AudioEncoder { +public: + HiFiEncoder(int sampleRate, int numChannels) : AudioEncoder(sampleRate, numChannels) { + _encodedSize = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * sizeof(int16_t) * numChannels) / 4; // codec reduces by 1/4th + } + + virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) override { + encodedBuffer.resize(_encodedSize); + AudioEncoder::process((const int16_t*)decodedBuffer.constData(), (int16_t*)encodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); + } +private: + int _encodedSize; +}; + +class HiFiDecoder : public Decoder, public AudioDecoder { +public: + HiFiDecoder(int sampleRate, int numChannels) : AudioDecoder(sampleRate, numChannels) { + _decodedSize = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * sizeof(int16_t) * numChannels; + } + + virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) override { + decodedBuffer.resize(_decodedSize); + AudioDecoder::process((const int16_t*)encodedBuffer.constData(), (int16_t*)decodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL, true); + } + + virtual void trackLostFrames(int numFrames) override { + QByteArray encodedBuffer; + QByteArray decodedBuffer; + decodedBuffer.resize(_decodedSize); + // NOTE: we don't actually use the results of this decode, we just do it to keep the state of the codec clean + AudioDecoder::process((const int16_t*)encodedBuffer.constData(), (int16_t*)decodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL, false); + } +private: + int _decodedSize; +}; + +Encoder* HiFiCodec::createEncoder(int sampleRate, int numChannels) { + return new HiFiEncoder(sampleRate, numChannels); +} + +Decoder* HiFiCodec::createDecoder(int sampleRate, int numChannels) { + return new HiFiDecoder(sampleRate, numChannels); +} + +void HiFiCodec::releaseEncoder(Encoder* encoder) { + delete encoder; +} + +void HiFiCodec::releaseDecoder(Decoder* decoder) { + delete decoder; +} diff --git a/plugins/hifiCodec/src/HiFiCodec.h b/plugins/hifiCodec/src/HiFiCodec.h new file mode 100644 index 0000000000..eeba8d56d8 --- /dev/null +++ b/plugins/hifiCodec/src/HiFiCodec.h @@ -0,0 +1,42 @@ +// +// HiFiCodec.h +// plugins/hifiCodec/src +// +// Created by Brad Hefta-Gaub on 7/10/2016 +// Copyright 2016 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_HiFiCodec_h +#define hifi_HiFiCodec_h + +#include + +class HiFiCodec : public CodecPlugin { + Q_OBJECT + +public: + // Plugin functions + bool isSupported() const override; + const QString& getName() const override { return NAME; } + + void init() override; + void deinit() override; + + /// Called when a plugin is being activated for use. May be called multiple times. + bool activate() override; + /// Called when a plugin is no longer being used. May be called multiple times. + void deactivate() override; + + virtual Encoder* createEncoder(int sampleRate, int numChannels) override; + virtual Decoder* createDecoder(int sampleRate, int numChannels) override; + virtual void releaseEncoder(Encoder* encoder) override; + virtual void releaseDecoder(Decoder* decoder) override; + +private: + static const QString NAME; +}; + +#endif // hifi_HiFiCodec_h diff --git a/plugins/hifiCodec/src/HiFiCodecProvider.cpp b/plugins/hifiCodec/src/HiFiCodecProvider.cpp new file mode 100644 index 0000000000..b9698cc821 --- /dev/null +++ b/plugins/hifiCodec/src/HiFiCodecProvider.cpp @@ -0,0 +1,46 @@ +// +// Created by Brad Hefta-Gaub on 7/10/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include +#include + +#include +#include + +#include "HiFiCodec.h" + +class HiFiCodecProvider : public QObject, public CodecProvider { + Q_OBJECT + Q_PLUGIN_METADATA(IID CodecProvider_iid FILE "plugin.json") + Q_INTERFACES(CodecProvider) + +public: + HiFiCodecProvider(QObject* parent = nullptr) : QObject(parent) {} + virtual ~HiFiCodecProvider() {} + + virtual CodecPluginList getCodecPlugins() override { + static std::once_flag once; + std::call_once(once, [&] { + + CodecPluginPointer hiFiCodec(new HiFiCodec()); + if (hiFiCodec->isSupported()) { + _codecPlugins.push_back(hiFiCodec); + } + + }); + return _codecPlugins; + } + +private: + CodecPluginList _codecPlugins; +}; + +#include "HiFiCodecProvider.moc" diff --git a/plugins/hifiCodec/src/plugin.json b/plugins/hifiCodec/src/plugin.json new file mode 100644 index 0000000000..df26a67ea8 --- /dev/null +++ b/plugins/hifiCodec/src/plugin.json @@ -0,0 +1 @@ +{"name":"HiFi 4:1 Audio Codec"} diff --git a/plugins/hifiNeuron/CMakeLists.txt b/plugins/hifiNeuron/CMakeLists.txt index 1cab2359a9..a9ed8cca6e 100644 --- a/plugins/hifiNeuron/CMakeLists.txt +++ b/plugins/hifiNeuron/CMakeLists.txt @@ -6,8 +6,11 @@ # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # -set(TARGET_NAME hifiNeuron) -setup_hifi_plugin(Script Qml Widgets) -link_hifi_libraries(shared controllers ui plugins input-plugins) -target_neuron() +if (APPLE OR WIN32) + + set(TARGET_NAME hifiNeuron) + setup_hifi_plugin(Script Qml Widgets) + link_hifi_libraries(shared controllers ui plugins input-plugins) + target_neuron() +endif() diff --git a/plugins/hifiNeuron/src/NeuronPlugin.cpp b/plugins/hifiNeuron/src/NeuronPlugin.cpp index 6e2f744173..e41472a8c5 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.cpp +++ b/plugins/hifiNeuron/src/NeuronPlugin.cpp @@ -25,9 +25,7 @@ Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") #define __OS_XUN__ 1 #define BOOL int -#ifdef HAVE_NEURON #include -#endif const QString NeuronPlugin::NAME = "Neuron"; const QString NeuronPlugin::NEURON_ID_STRING = "Perception Neuron"; @@ -166,69 +164,6 @@ static controller::StandardPoseChannel neuronJointIndexToPoseIndexMap[NeuronJoin static glm::vec3 rightHandThumb1DefaultAbsTranslation(-2.155500650405884, -0.7610001564025879, 2.685631036758423); static glm::vec3 leftHandThumb1DefaultAbsTranslation(2.1555817127227783, -0.7603635787963867, 2.6856393814086914); -// default translations (cm) -static glm::vec3 neuronJointTranslations[NeuronJointIndex::Size] = { - {131.901, 95.6602, -27.9815}, - {-9.55907, -1.58772, 0.0760284}, - {0.0144232, -41.4683, -0.105322}, - {1.59348, -41.5875, -0.557237}, - {9.72077, -1.68926, -0.280643}, - {0.0886684, -43.1586, -0.0111596}, - {-2.98473, -44.0517, 0.0694456}, - {0.110967, 16.3959, 0.140463}, - {0.0500451, 10.0238, 0.0731921}, - {0.061568, 10.4352, 0.0583075}, - {0.0500606, 10.0217, 0.0711083}, - {0.0317731, 10.7176, 0.0779325}, - {-0.0204253, 9.71067, 0.131734}, - {-3.24245, 7.13584, 0.185638}, - {-13.0885, -0.0877601, 0.176065}, - {-27.2674, 0.0688724, 0.0272146}, - {-26.7673, 0.0301916, 0.0102847}, - {-2.56017, 0.195537, 3.20968}, - {-3.78796, 0, 0}, - {-2.63141, 0, 0}, - {-3.31579, 0.522947, 2.03495}, - {-5.36589, -0.0939789, 1.02771}, - {-3.72278, 0, 0}, - {-2.11074, 0, 0}, - {-3.47874, 0.532042, 0.778358}, - {-5.32194, -0.0864, 0.322863}, - {-4.06232, 0, 0}, - {-2.54653, 0, 0}, - {-3.46131, 0.553263, -0.132632}, - {-4.76716, -0.0227368, -0.492632}, - {-3.54073, 0, 0}, - {-2.45634, 0, 0}, - {-3.25137, 0.482779, -1.23613}, - {-4.25937, -0.0227368, -1.12168}, - {-2.83528, 0, 0}, - {-1.79166, 0, 0}, - {3.25624, 7.13148, -0.131575}, - {13.149, -0.052598, -0.125076}, - {27.2903, 0.00282644, -0.0181535}, - {26.6602, 0.000969969, -0.0487599}, - {2.56017, 0.195537, 3.20968}, - {3.78796, 0, 0}, - {2.63141, 0, 0}, - {3.31579, 0.522947, 2.03495}, - {5.36589, -0.0939789, 1.02771}, - {3.72278, 0, 0}, - {2.11074, 0, 0}, - {3.47874, 0.532042, 0.778358}, - {5.32194, -0.0864, 0.322863}, - {4.06232, 0, 0}, - {2.54653, 0, 0}, - {3.46131, 0.553263, -0.132632}, - {4.76716, -0.0227368, -0.492632}, - {3.54073, 0, 0}, - {2.45634, 0, 0}, - {3.25137, 0.482779, -1.23613}, - {4.25937, -0.0227368, -1.12168}, - {2.83528, 0, 0}, - {1.79166, 0, 0} -}; - static controller::StandardPoseChannel neuronJointIndexToPoseIndex(NeuronJointIndex i) { assert(i >= 0 && i < NeuronJointIndex::Size); if (i >= 0 && i < NeuronJointIndex::Size) { @@ -307,16 +242,13 @@ static const char* controllerJointName(controller::StandardPoseChannel i) { // convert between YXZ neuron euler angles in degrees to quaternion // this is the default setting in the Axis Neuron server. -static quat eulerToQuat(vec3 euler) { +static quat eulerToQuat(const vec3& e) { // euler.x and euler.y are swaped, WTF. - glm::vec3 e = glm::vec3(euler.y, euler.x, euler.z) * RADIANS_PER_DEGREE; - return (glm::angleAxis(e.y, Vectors::UNIT_Y) * - glm::angleAxis(e.x, Vectors::UNIT_X) * - glm::angleAxis(e.z, Vectors::UNIT_Z)); + return (glm::angleAxis(e.x * RADIANS_PER_DEGREE, Vectors::UNIT_Y) * + glm::angleAxis(e.y * RADIANS_PER_DEGREE, Vectors::UNIT_X) * + glm::angleAxis(e.z * RADIANS_PER_DEGREE, Vectors::UNIT_Z)); } -#ifdef HAVE_NEURON - // // neuronDataReader SDK callback functions // @@ -355,21 +287,6 @@ void FrameDataReceivedCallback(void* context, SOCKET_REF sender, BvhDataHeaderEx // copy the data memcpy(&(neuronPlugin->_joints[0]), data, sizeof(NeuronPlugin::NeuronJoint) * NUM_JOINTS); - - } else { - qCWarning(inputplugins) << "NeuronPlugin: unsuported binary format, please enable displacements"; - - // enter mutex - std::lock_guard guard(neuronPlugin->_jointsMutex); - - if (neuronPlugin->_joints.size() != NeuronJointIndex::Size) { - neuronPlugin->_joints.resize(NeuronJointIndex::Size, { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }); - } - - for (int i = 0; i < NeuronJointIndex::Size; i++) { - neuronPlugin->_joints[i].euler = glm::vec3(); - neuronPlugin->_joints[i].pos = neuronJointTranslations[i]; - } } } else { static bool ONCE = false; @@ -435,26 +352,19 @@ static void SocketStatusChangedCallback(void* context, SOCKET_REF sender, Socket qCDebug(inputplugins) << "NeuronPlugin: socket status = " << message; } -#endif // #ifdef HAVE_NEURON - // // NeuronPlugin // bool NeuronPlugin::isSupported() const { -#ifdef HAVE_NEURON // Because it's a client/server network architecture, we can't tell // if the neuron is actually connected until we connect to the server. return true; -#else - return false; -#endif } bool NeuronPlugin::activate() { InputPlugin::activate(); -#ifdef HAVE_NEURON // register with userInputMapper auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(_inputDevice); @@ -477,16 +387,14 @@ bool NeuronPlugin::activate() { } else { qCDebug(inputplugins) << "NeuronPlugin: success connecting to " << _serverAddress.c_str() << ":" << _serverPort; + emit deviceConnected(getName()); + BRRegisterAutoSyncParmeter(_socketRef, Cmd_CombinationMode); return true; } -#else - return false; -#endif } void NeuronPlugin::deactivate() { -#ifdef HAVE_NEURON // unregister from userInputMapper if (_inputDevice->_deviceID != controller::Input::INVALID_DEVICE) { auto userInputMapper = DependencyManager::get(); @@ -499,10 +407,9 @@ void NeuronPlugin::deactivate() { } InputPlugin::deactivate(); -#endif } -void NeuronPlugin::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void NeuronPlugin::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { std::vector joints; { // lock and copy @@ -548,16 +455,23 @@ QString NeuronPlugin::InputDevice::getDefaultMappingConfig() const { void NeuronPlugin::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const std::vector& joints, const std::vector& prevJoints) { for (size_t i = 0; i < joints.size(); i++) { + int poseIndex = neuronJointIndexToPoseIndex((NeuronJointIndex)i); glm::vec3 linearVel, angularVel; - glm::vec3 pos = joints[i].pos; - glm::quat rot = eulerToQuat(joints[i].euler); + const glm::vec3& pos = joints[i].pos; + const glm::vec3& rotEuler = joints[i].euler; + + if (Vectors::ZERO == pos && Vectors::ZERO == rotEuler) { + _poseStateMap[poseIndex] = controller::Pose(); + continue; + } + + glm::quat rot = eulerToQuat(rotEuler); if (i < prevJoints.size()) { linearVel = (pos - (prevJoints[i].pos * METERS_PER_CENTIMETER)) / deltaTime; // m/s // quat log imaginary part points along the axis of rotation, with length of one half the angle of rotation. glm::quat d = glm::log(rot * glm::inverse(eulerToQuat(prevJoints[i].euler))); angularVel = glm::vec3(d.x, d.y, d.z) / (0.5f * deltaTime); // radians/s } - int poseIndex = neuronJointIndexToPoseIndex((NeuronJointIndex)i); _poseStateMap[poseIndex] = controller::Pose(pos, rot, linearVel, angularVel); } diff --git a/plugins/hifiNeuron/src/NeuronPlugin.h b/plugins/hifiNeuron/src/NeuronPlugin.h index 99859dcacb..9ddd79c013 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.h +++ b/plugins/hifiNeuron/src/NeuronPlugin.h @@ -27,7 +27,6 @@ public: // Plugin functions virtual bool isSupported() const override; - virtual bool isJointController() const override { return true; } virtual const QString& getName() const override { return NAME; } const QString& getID() const override { return NEURON_ID_STRING; } @@ -35,7 +34,7 @@ public: virtual void deactivate() override; virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void saveSettings() const override; virtual void loadSettings() override; @@ -56,7 +55,7 @@ protected: // Device functions virtual controller::Input::NamedVector getAvailableInputs() const override; virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override {}; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override {}; virtual void focusOutEvent() override {}; void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const std::vector& joints, const std::vector& prevJoints); diff --git a/plugins/hifiNeuron/src/plugin.json b/plugins/hifiNeuron/src/plugin.json index 0967ef424b..d153b5cebd 100644 --- a/plugins/hifiNeuron/src/plugin.json +++ b/plugins/hifiNeuron/src/plugin.json @@ -1 +1 @@ -{} +{"name":"Neuron"} diff --git a/plugins/hifiSdl2/src/Joystick.cpp b/plugins/hifiSdl2/src/Joystick.cpp index 9d195fd606..be4ad6e4ee 100644 --- a/plugins/hifiSdl2/src/Joystick.cpp +++ b/plugins/hifiSdl2/src/Joystick.cpp @@ -15,31 +15,36 @@ const float CONTROLLER_THRESHOLD = 0.3f; -#ifdef HAVE_SDL2 const float MAX_AXIS = 32768.0f; Joystick::Joystick(SDL_JoystickID instanceId, SDL_GameController* sdlGameController) : InputDevice("GamePad"), _sdlGameController(sdlGameController), _sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)), + _sdlHaptic(SDL_HapticOpenFromJoystick(_sdlJoystick)), _instanceId(instanceId) { - + if (!_sdlHaptic) { + qDebug() << "SDL Haptic Open Failure: " << QString(SDL_GetError()); + } else { + if (SDL_HapticRumbleInit(_sdlHaptic) != 0) { + qDebug() << "SDL Haptic Rumble Init Failure: " << QString(SDL_GetError()); + } + } } -#endif - Joystick::~Joystick() { closeJoystick(); } void Joystick::closeJoystick() { -#ifdef HAVE_SDL2 + if (_sdlHaptic) { + SDL_HapticClose(_sdlHaptic); + } SDL_GameControllerClose(_sdlGameController); -#endif } -void Joystick::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void Joystick::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { for (auto axisState : _axisStateMap) { if (fabsf(axisState.second) < CONTROLLER_THRESHOLD) { _axisStateMap[axisState.first] = 0.0f; @@ -52,8 +57,6 @@ void Joystick::focusOutEvent() { _buttonPressedMap.clear(); }; -#ifdef HAVE_SDL2 - void Joystick::handleAxisEvent(const SDL_ControllerAxisEvent& event) { SDL_GameControllerAxis axis = (SDL_GameControllerAxis) event.axis; _axisStateMap[makeInput((controller::StandardAxisChannel)axis).getChannel()] = (float)event.value / MAX_AXIS; @@ -69,57 +72,64 @@ void Joystick::handleButtonEvent(const SDL_ControllerButtonEvent& event) { } } -#endif +bool Joystick::triggerHapticPulse(float strength, float duration, controller::Hand hand) { + if (SDL_HapticRumblePlay(_sdlHaptic, strength, duration) != 0) { + return false; + } + return true; +} controller::Input::NamedVector Joystick::getAvailableInputs() const { using namespace controller; - static const Input::NamedVector availableInputs{ - makePair(A, "A"), - makePair(B, "B"), - makePair(X, "X"), - makePair(Y, "Y"), - // DPad - makePair(DU, "DU"), - makePair(DD, "DD"), - makePair(DL, "DL"), - makePair(DR, "DR"), - // Bumpers - makePair(LB, "LB"), - makePair(RB, "RB"), - // Stick press - makePair(LS, "LS"), - makePair(RS, "RS"), - // Center buttons - makePair(START, "Start"), - makePair(BACK, "Back"), - // Analog sticks - makePair(LX, "LX"), - makePair(LY, "LY"), - makePair(RX, "RX"), - makePair(RY, "RY"), - - // Triggers - makePair(LT, "LT"), - makePair(RT, "RT"), + if (_availableInputs.length() == 0) { + _availableInputs = { + makePair(A, "A"), + makePair(B, "B"), + makePair(X, "X"), + makePair(Y, "Y"), + // DPad + makePair(DU, "DU"), + makePair(DD, "DD"), + makePair(DL, "DL"), + makePair(DR, "DR"), + // Bumpers + makePair(LB, "LB"), + makePair(RB, "RB"), + // Stick press + makePair(LS, "LS"), + makePair(RS, "RS"), + // Center buttons + makePair(START, "Start"), + makePair(BACK, "Back"), + // Analog sticks + makePair(LX, "LX"), + makePair(LY, "LY"), + makePair(RX, "RX"), + makePair(RY, "RY"), - // Aliases, PlayStation style names - makePair(LB, "L1"), - makePair(RB, "R1"), - makePair(LT, "L2"), - makePair(RT, "R2"), - makePair(LS, "L3"), - makePair(RS, "R3"), - makePair(BACK, "Select"), - makePair(A, "Cross"), - makePair(B, "Circle"), - makePair(X, "Square"), - makePair(Y, "Triangle"), - makePair(DU, "Up"), - makePair(DD, "Down"), - makePair(DL, "Left"), - makePair(DR, "Right"), - }; - return availableInputs; + // Triggers + makePair(LT, "LT"), + makePair(RT, "RT"), + + // Aliases, PlayStation style names + makePair(LB, "L1"), + makePair(RB, "R1"), + makePair(LT, "L2"), + makePair(RT, "R2"), + makePair(LS, "L3"), + makePair(RS, "R3"), + makePair(BACK, "Select"), + makePair(A, "Cross"), + makePair(B, "Circle"), + makePair(X, "Square"), + makePair(Y, "Triangle"), + makePair(DU, "Up"), + makePair(DD, "Down"), + makePair(DL, "Left"), + makePair(DR, "Right"), + }; + } + return _availableInputs; } QString Joystick::getDefaultMappingConfig() const { diff --git a/plugins/hifiSdl2/src/Joystick.h b/plugins/hifiSdl2/src/Joystick.h index 08bf27b960..25381d545a 100644 --- a/plugins/hifiSdl2/src/Joystick.h +++ b/plugins/hifiSdl2/src/Joystick.h @@ -15,10 +15,8 @@ #include #include -#ifdef HAVE_SDL2 #include #undef main -#endif #include #include @@ -26,10 +24,7 @@ class Joystick : public QObject, public controller::InputDevice { Q_OBJECT Q_PROPERTY(QString name READ getName) - -#ifdef HAVE_SDL2 Q_PROPERTY(int instanceId READ getInstanceId) -#endif public: using Pointer = std::shared_ptr; @@ -39,33 +34,30 @@ public: // Device functions virtual controller::Input::NamedVector getAvailableInputs() const override; virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void focusOutEvent() override; + + bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; Joystick() : InputDevice("GamePad") {} ~Joystick(); -#ifdef HAVE_SDL2 Joystick(SDL_JoystickID instanceId, SDL_GameController* sdlGameController); -#endif void closeJoystick(); -#ifdef HAVE_SDL2 void handleAxisEvent(const SDL_ControllerAxisEvent& event); void handleButtonEvent(const SDL_ControllerButtonEvent& event); -#endif -#ifdef HAVE_SDL2 int getInstanceId() const { return _instanceId; } -#endif private: -#ifdef HAVE_SDL2 SDL_GameController* _sdlGameController; SDL_Joystick* _sdlJoystick; + SDL_Haptic* _sdlHaptic; SDL_JoystickID _instanceId; -#endif + + mutable controller::Input::NamedVector _availableInputs; }; #endif // hifi_Joystick_h diff --git a/plugins/hifiSdl2/src/SDL2Manager.cpp b/plugins/hifiSdl2/src/SDL2Manager.cpp index 7091b20d21..b9a19658e2 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.cpp +++ b/plugins/hifiSdl2/src/SDL2Manager.cpp @@ -16,7 +16,6 @@ #include "SDL2Manager.h" -#ifdef HAVE_SDL2 static_assert( (int)controller::A == (int)SDL_CONTROLLER_BUTTON_A && (int)controller::B == (int)SDL_CONTROLLER_BUTTON_B && @@ -40,29 +39,17 @@ static_assert( (int)controller::LT == (int)SDL_CONTROLLER_AXIS_TRIGGERLEFT && (int)controller::RT == (int)SDL_CONTROLLER_AXIS_TRIGGERRIGHT, "SDL2 equvalence: Enums and values from StandardControls.h are assumed to match enums from SDL_gamecontroller.h"); -#endif const QString SDL2Manager::NAME = "SDL2"; -#ifdef HAVE_SDL2 SDL_JoystickID SDL2Manager::getInstanceId(SDL_GameController* controller) { SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller); return SDL_JoystickInstanceID(joystick); } -#endif - -SDL2Manager::SDL2Manager() : -#ifdef HAVE_SDL2 -_openJoysticks(), -#endif -_isInitialized(false) -{ -} void SDL2Manager::init() { -#ifdef HAVE_SDL2 - bool initSuccess = (SDL_Init(SDL_INIT_GAMECONTROLLER) == 0); + bool initSuccess = (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) == 0); if (initSuccess) { int joystickCount = SDL_NumJoysticks(); @@ -79,6 +66,7 @@ void SDL2Manager::init() { auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(joystick); emit joystickAdded(joystick.get()); + emit subdeviceConnected(getName(), SDL_GameControllerName(controller)); } } } @@ -88,66 +76,50 @@ void SDL2Manager::init() { else { qDebug() << "Error initializing SDL2 Manager"; } -#endif } void SDL2Manager::deinit() { -#ifdef HAVE_SDL2 _openJoysticks.clear(); SDL_Quit(); -#endif } bool SDL2Manager::activate() { InputPlugin::activate(); -#ifdef HAVE_SDL2 auto userInputMapper = DependencyManager::get(); for (auto joystick : _openJoysticks) { userInputMapper->registerDevice(joystick); emit joystickAdded(joystick.get()); } return true; -#else - return false; -#endif } void SDL2Manager::deactivate() { -#ifdef HAVE_SDL2 auto userInputMapper = DependencyManager::get(); for (auto joystick : _openJoysticks) { userInputMapper->removeDevice(joystick->getDeviceID()); emit joystickRemoved(joystick.get()); } -#endif InputPlugin::deactivate(); } bool SDL2Manager::isSupported() const { -#ifdef HAVE_SDL2 return true; -#else - return false; -#endif } void SDL2Manager::pluginFocusOutEvent() { -#ifdef HAVE_SDL2 for (auto joystick : _openJoysticks) { joystick->focusOutEvent(); } -#endif } -void SDL2Manager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { -#ifdef HAVE_SDL2 +void SDL2Manager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { if (_isInitialized) { auto userInputMapper = DependencyManager::get(); for (auto joystick : _openJoysticks) { - joystick->update(deltaTime, inputCalibrationData, jointsCaptured); + joystick->update(deltaTime, inputCalibrationData); } PerformanceTimer perfTimer("SDL2Manager::update"); @@ -186,6 +158,7 @@ void SDL2Manager::pluginUpdate(float deltaTime, const controller::InputCalibrati _openJoysticks[id] = joystick; userInputMapper->registerDevice(joystick); emit joystickAdded(joystick.get()); + emit subdeviceConnected(getName(), SDL_GameControllerName(controller)); } } else if (event.type == SDL_CONTROLLERDEVICEREMOVED) { if (_openJoysticks.contains(event.cdevice.which)) { @@ -197,5 +170,4 @@ void SDL2Manager::pluginUpdate(float deltaTime, const controller::InputCalibrati } } } -#endif } diff --git a/plugins/hifiSdl2/src/SDL2Manager.h b/plugins/hifiSdl2/src/SDL2Manager.h index f69e23ee98..a597a87aee 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.h +++ b/plugins/hifiSdl2/src/SDL2Manager.h @@ -12,9 +12,7 @@ #ifndef hifi__SDL2Manager_h #define hifi__SDL2Manager_h -#ifdef HAVE_SDL2 #include -#endif #include #include @@ -24,30 +22,26 @@ class SDL2Manager : public InputPlugin { Q_OBJECT public: - SDL2Manager(); - // Plugin functions - virtual bool isSupported() const override; - virtual bool isJointController() const override { return false; } - virtual const QString& getName() const override { return NAME; } + bool isSupported() const override; + const QString& getName() const override { return NAME; } - virtual void init() override; - virtual void deinit() override; + void init() override; + void deinit() override; /// Called when a plugin is being activated for use. May be called multiple times. - virtual bool activate() override; + bool activate() override; /// Called when a plugin is no longer being used. May be called multiple times. - virtual void deactivate() override; + void deactivate() override; - virtual void pluginFocusOutEvent() override; - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void pluginFocusOutEvent() override; + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; signals: void joystickAdded(Joystick* joystick); void joystickRemoved(Joystick* joystick); private: -#ifdef HAVE_SDL2 SDL_JoystickID getInstanceId(SDL_GameController* controller); int axisInvalid() const { return SDL_CONTROLLER_AXIS_INVALID; } @@ -81,8 +75,7 @@ private: int buttonRelease() const { return SDL_RELEASED; } QMap _openJoysticks; -#endif - bool _isInitialized; + bool _isInitialized { false } ; static const QString NAME; }; diff --git a/plugins/hifiSdl2/src/plugin.json b/plugins/hifiSdl2/src/plugin.json index 0967ef424b..a65846ecab 100644 --- a/plugins/hifiSdl2/src/plugin.json +++ b/plugins/hifiSdl2/src/plugin.json @@ -1 +1 @@ -{} +{"name":"SDL2"} diff --git a/plugins/hifiSixense/CMakeLists.txt b/plugins/hifiSixense/CMakeLists.txt index 589b5b8964..f907d7865f 100644 --- a/plugins/hifiSixense/CMakeLists.txt +++ b/plugins/hifiSixense/CMakeLists.txt @@ -8,5 +8,5 @@ set(TARGET_NAME hifiSixense) setup_hifi_plugin(Script Qml Widgets) -link_hifi_libraries(shared controllers ui plugins input-plugins) +link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins) target_sixense() diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index fdb7bb17fe..baf13f1fae 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include @@ -66,14 +66,8 @@ const QString SHOW_DEBUG_RAW = "Debug Draw Raw Data"; const QString SHOW_DEBUG_CALIBRATED = "Debug Draw Calibrated Data"; bool SixenseManager::isSupported() const { -#ifdef HAVE_SIXENSE - -#if defined(Q_OS_OSX) - return QSysInfo::macVersion() <= QSysInfo::MV_MAVERICKS; -#else +#if defined(HAVE_SIXENSE) && !defined(Q_OS_OSX) return true; -#endif - #else return false; #endif @@ -83,6 +77,7 @@ bool SixenseManager::activate() { InputPlugin::activate(); #ifdef HAVE_SIXENSE + #if !defined(Q_OS_LINUX) _container->addMenu(MENU_PATH); _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, TOGGLE_SMOOTH, [this] (bool clicked) { setSixenseFilter(clicked); }, @@ -95,6 +90,7 @@ bool SixenseManager::activate() { _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, SHOW_DEBUG_CALIBRATED, [this] (bool clicked) { _inputDevice->setDebugDrawCalibrated(clicked); }, true, false); + #endif auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(_inputDevice); @@ -112,8 +108,10 @@ void SixenseManager::deactivate() { InputPlugin::deactivate(); #ifdef HAVE_SIXENSE + #if !defined(Q_OS_LINUX) _container->removeMenuItem(MENU_NAME, TOGGLE_SMOOTH); _container->removeMenu(MENU_PATH); + #endif _inputDevice->_poseStateMap.clear(); @@ -134,21 +132,29 @@ void SixenseManager::setSixenseFilter(bool filter) { #endif } -void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { BAIL_IF_NOT_LOADED +#ifdef HAVE_SIXENSE + static bool sixenseHasBeenConnected { false }; + if (!sixenseHasBeenConnected && sixenseIsBaseConnected(0)) { + sixenseHasBeenConnected = true; + emit deviceConnected(getName()); + } + auto userInputMapper = DependencyManager::get(); userInputMapper->withLock([&, this]() { - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + _inputDevice->update(deltaTime, inputCalibrationData); }); if (_inputDevice->_requestReset) { _container->requestReset(); _inputDevice->_requestReset = false; } +#endif } -void SixenseManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void SixenseManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { BAIL_IF_NOT_LOADED #ifdef HAVE_SIXENSE _buttonPressedMap.clear(); @@ -208,14 +214,10 @@ void SixenseManager::InputDevice::update(float deltaTime, const controller::Inpu _axisStateMap[left ? LY : RY] = data->joystick_y; _axisStateMap[left ? LT : RT] = data->trigger; - if (!jointsCaptured) { - // Rotation of Palm - glm::quat rotation(data->rot_quat[3], data->rot_quat[0], data->rot_quat[1], data->rot_quat[2]); - handlePoseEvent(deltaTime, inputCalibrationData, position, rotation, left); - rawPoses[i] = controller::Pose(position, rotation, Vectors::ZERO, Vectors::ZERO); - } else { - _poseStateMap.clear(); - } + // Rotation of Palm + glm::quat rotation(data->rot_quat[3], data->rot_quat[0], data->rot_quat[1], data->rot_quat[2]); + handlePoseEvent(deltaTime, inputCalibrationData, position, rotation, left); + rawPoses[i] = controller::Pose(position, rotation, Vectors::ZERO, Vectors::ZERO); } else { auto hand = left ? controller::StandardPoseChannel::LEFT_HAND : controller::StandardPoseChannel::RIGHT_HAND; _poseStateMap[hand] = controller::Pose(); diff --git a/plugins/hifiSixense/src/SixenseManager.h b/plugins/hifiSixense/src/SixenseManager.h index a46614b17a..6aec9fd4ad 100644 --- a/plugins/hifiSixense/src/SixenseManager.h +++ b/plugins/hifiSixense/src/SixenseManager.h @@ -28,7 +28,6 @@ class SixenseManager : public InputPlugin { public: // Plugin functions virtual bool isSupported() const override; - virtual bool isJointController() const override { return true; } virtual const QString& getName() const override { return NAME; } virtual const QString& getID() const override { return HYDRA_ID_STRING; } @@ -36,7 +35,7 @@ public: virtual void deactivate() override; virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void saveSettings() const override; virtual void loadSettings() override; @@ -61,7 +60,7 @@ private: // Device functions virtual controller::Input::NamedVector getAvailableInputs() const override; virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void focusOutEvent() override; void handleButtonEvent(unsigned int buttons, bool left); diff --git a/plugins/hifiSixense/src/SixenseSupportOSX.cpp b/plugins/hifiSixense/src/SixenseSupportOSX.cpp deleted file mode 100644 index fce2ea023b..0000000000 --- a/plugins/hifiSixense/src/SixenseSupportOSX.cpp +++ /dev/null @@ -1,155 +0,0 @@ -// -// SixenseSupportOSX.cpp -// libraries/input-plugins/src/input-plugins -// -// Created by Clement 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 -// - -// Mock implementation of sixense.h to hide dynamic linking on OS X -#if defined(__APPLE__) && defined(HAVE_SIXENSE) -#include - -#include - -#include -#include -#include - -#ifndef SIXENSE_LIB_FILENAME -#define SIXENSE_LIB_FILENAME QCoreApplication::applicationDirPath() + "/../Frameworks/libsixense_x64" -#endif - -using Library = std::unique_ptr; -static Library SIXENSE; - -struct Callable { - template - int operator() (Args&&... args){ - return reinterpret_cast(function)(std::forward(args)...); - } - QFunctionPointer function; -}; - -Callable resolve(const Library& library, const char* name) { - Q_ASSERT_X(library && library->isLoaded(), __FUNCTION__, "Sixense library not loaded"); - auto function = library->resolve(name); - Q_ASSERT_X(function, __FUNCTION__, std::string("Could not resolve ").append(name).c_str()); - return Callable { function }; -} -#define FORWARD resolve(SIXENSE, __FUNCTION__) - - -void loadSixense() { - Q_ASSERT_X(!(SIXENSE && SIXENSE->isLoaded()), __FUNCTION__, "Sixense library already loaded"); - SIXENSE.reset(new QLibrary(SIXENSE_LIB_FILENAME)); - Q_CHECK_PTR(SIXENSE); - - if (SIXENSE->load()){ - qDebug() << "Loaded sixense library for hydra support -" << SIXENSE->fileName(); - } else { - qDebug() << "Sixense library at" << SIXENSE->fileName() << "failed to load:" << SIXENSE->errorString(); - qDebug() << "Continuing without hydra support."; - } -} -void unloadSixense() { - SIXENSE->unload(); -} - - -// sixense.h wrapper for OSX dynamic linking -int sixenseInit() { - loadSixense(); - if (!SIXENSE || !SIXENSE->isLoaded()) { - return SIXENSE_FAILURE; - } - return FORWARD(); -} -int sixenseExit() { - auto returnCode = FORWARD(); - unloadSixense(); - return returnCode; -} - -int sixenseGetMaxBases() { - return FORWARD(); -} -int sixenseSetActiveBase(int i) { - return FORWARD(i); -} -int sixenseIsBaseConnected(int i) { - return FORWARD(i); -} - -int sixenseGetMaxControllers() { - return FORWARD(); -} -int sixenseIsControllerEnabled(int which) { - return FORWARD(which); -} -int sixenseGetNumActiveControllers() { - return FORWARD(); -} - -int sixenseGetHistorySize() { - return FORWARD(); -} - -int sixenseGetData(int which, int index_back, sixenseControllerData* data) { - return FORWARD(which, index_back, data); -} -int sixenseGetAllData(int index_back, sixenseAllControllerData* data) { - return FORWARD(index_back, data); -} -int sixenseGetNewestData(int which, sixenseControllerData* data) { - return FORWARD(which, data); -} -int sixenseGetAllNewestData(sixenseAllControllerData* data) { - return FORWARD(data); -} - -int sixenseSetHemisphereTrackingMode(int which_controller, int state) { - return FORWARD(which_controller, state); -} -int sixenseGetHemisphereTrackingMode(int which_controller, int* state) { - return FORWARD(which_controller, state); -} -int sixenseAutoEnableHemisphereTracking(int which_controller) { - return FORWARD(which_controller); -} - -int sixenseSetHighPriorityBindingEnabled(int on_or_off) { - return FORWARD(on_or_off); -} -int sixenseGetHighPriorityBindingEnabled(int* on_or_off) { - return FORWARD(on_or_off); -} - -int sixenseTriggerVibration(int controller_id, int duration_100ms, int pattern_id) { - return FORWARD(controller_id, duration_100ms, pattern_id); -} - -int sixenseSetFilterEnabled(int on_or_off) { - return FORWARD(on_or_off); -} -int sixenseGetFilterEnabled(int* on_or_off) { - return FORWARD(on_or_off); -} - -int sixenseSetFilterParams(float near_range, float near_val, float far_range, float far_val) { - return FORWARD(near_range, near_val, far_range, far_val); -} -int sixenseGetFilterParams(float* near_range, float* near_val, float* far_range, float* far_val) { - return FORWARD(near_range, near_val, far_range, far_val); -} - -int sixenseSetBaseColor(unsigned char red, unsigned char green, unsigned char blue) { - return FORWARD(red, green, blue); -} -int sixenseGetBaseColor(unsigned char* red, unsigned char* green, unsigned char* blue) { - return FORWARD(red, green, blue); -} -#endif diff --git a/plugins/hifiSixense/src/plugin.json b/plugins/hifiSixense/src/plugin.json index 0967ef424b..9e6e15a354 100644 --- a/plugins/hifiSixense/src/plugin.json +++ b/plugins/hifiSixense/src/plugin.json @@ -1 +1 @@ -{} +{"name":"Sixense"} diff --git a/plugins/hifiSpacemouse/CMakeLists.txt b/plugins/hifiSpacemouse/CMakeLists.txt new file mode 100644 index 0000000000..bcfb309a69 --- /dev/null +++ b/plugins/hifiSpacemouse/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Created by Bradley Austin Davis on 2016/05/11 +# Copyright 2013-2016 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 +# + +if(WIN32) + set(TARGET_NAME hifiSpacemouse) + find_package(3DCONNEXIONCLIENT) + if (3DCONNEXIONCLIENT_FOUND) + setup_hifi_plugin(Script Qml Widgets) + link_hifi_libraries(shared networking controllers ui plugins input-plugins) + target_include_directories(${TARGET_NAME} PUBLIC ${3DCONNEXIONCLIENT_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${3DCONNEXIONCLIENT_LIBRARIES}) + endif() +endif() diff --git a/libraries/input-plugins/src/input-plugins/SpacemouseManager.cpp b/plugins/hifiSpacemouse/src/SpacemouseManager.cpp similarity index 94% rename from libraries/input-plugins/src/input-plugins/SpacemouseManager.cpp rename to plugins/hifiSpacemouse/src/SpacemouseManager.cpp index d946990319..4641799b79 100644 --- a/libraries/input-plugins/src/input-plugins/SpacemouseManager.cpp +++ b/plugins/hifiSpacemouse/src/SpacemouseManager.cpp @@ -11,19 +11,73 @@ #include "SpacemouseManager.h" +#ifdef Q_OS_WIN +#include +#endif + #include #include -#include #include -#include "../../../interface/src/Menu.h" +const QString SpacemouseManager::NAME { "Spacemouse" }; const float MAX_AXIS = 75.0f; // max forward = 2x speed +#define LOGITECH_VENDOR_ID 0x46d -static std::shared_ptr instance = std::make_shared(); +#ifndef RIDEV_DEVNOTIFY +#define RIDEV_DEVNOTIFY 0x00002000 +#endif -SpacemouseDevice::SpacemouseDevice() : InputDevice("Spacemouse") +const int TRACE_RIDI_DEVICENAME = 0; +const int TRACE_RIDI_DEVICEINFO = 0; + +#ifdef _WIN64 +typedef unsigned __int64 QWORD; +#endif + +bool Is3dmouseAttached(); + +std::shared_ptr instance; + +bool SpacemouseManager::isSupported() const { + return Is3dmouseAttached(); +} + +bool SpacemouseManager::activate() { + fLast3dmouseInputTime = 0; + + InitializeRawInput(GetActiveWindow()); + + QAbstractEventDispatcher::instance()->installNativeEventFilter(this); + + if (!instance) { + instance = std::make_shared(); + } + + if (instance->getDeviceID() == controller::Input::INVALID_DEVICE) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->registerDevice(instance); + } + return true; +} + +void SpacemouseManager::deactivate() { + QAbstractEventDispatcher::instance()->removeNativeEventFilter(this); + int deviceid = instance->getDeviceID(); + auto userInputMapper = DependencyManager::get(); + userInputMapper->removeDevice(deviceid); +} + +void SpacemouseManager::pluginFocusOutEvent() { + instance->focusOutEvent(); +} + +void SpacemouseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + +} + +SpacemouseDevice::SpacemouseDevice() : InputDevice(SpacemouseManager::NAME) { } @@ -111,71 +165,78 @@ controller::Input::NamedPair SpacemouseDevice::makePair(SpacemouseDevice::Positi return controller::Input::NamedPair(makeInput(axis), name); } -void SpacemouseDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void SpacemouseDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { // the update is done in the SpacemouseManager class. // for windows in the nativeEventFilter the inputmapper is connected or registed or removed when an 3Dconnnexion device is attached or detached // for osx the api will call DeviceAddedHandler or DeviceRemoveHandler when a 3Dconnexion device is attached or detached } -void SpacemouseManager::ManagerFocusOutEvent() { - instance->focusOutEvent(); -} - -void SpacemouseManager::init() { -} - -#ifdef HAVE_3DCONNEXIONCLIENT - #ifdef Q_OS_WIN -#include - -void SpacemouseManager::toggleSpacemouse(bool shouldEnable) { - if (shouldEnable) { - init(); - } - if (!shouldEnable && instance->getDeviceID() != controller::Input::INVALID_DEVICE) { - destroy(); - } +bool SpacemouseManager::nativeEventFilter(const QByteArray& eventType, void* message, long* result) { + MSG* msg = static_cast< MSG * >(message); + return RawInputEventFilter(message, result); } -void SpacemouseManager::init() { - if (Menu::getInstance()->isOptionChecked(MenuOption::Connexion)) { - fLast3dmouseInputTime = 0; - InitializeRawInput(GetActiveWindow()); +//Get an initialized array of PRAWINPUTDEVICE for the 3D devices +//pNumDevices returns the number of devices to register. Currently this is always 1. +static PRAWINPUTDEVICE GetDevicesToRegister(unsigned int* pNumDevices) { + // Array of raw input devices to register + static RAWINPUTDEVICE sRawInputDevices[] = { + { 0x01, 0x08, 0x00, 0x00 } // Usage Page = 0x01 Generic Desktop Page, Usage Id= 0x08 Multi-axis Controller + }; - QAbstractEventDispatcher::instance()->installNativeEventFilter(this); + if (pNumDevices) { + *pNumDevices = sizeof(sRawInputDevices) / sizeof(sRawInputDevices[0]); + } - if (instance->getDeviceID() != controller::Input::INVALID_DEVICE) { - auto userInputMapper = DependencyManager::get(); - userInputMapper->registerDevice(instance); - UserActivityLogger::getInstance().connectedDevice("controller", "Spacemouse"); + return sRawInputDevices; +} + + +//Detect the 3D mouse +bool Is3dmouseAttached() { + unsigned int numDevicesOfInterest = 0; + PRAWINPUTDEVICE devicesToRegister = GetDevicesToRegister(&numDevicesOfInterest); + + unsigned int nDevices = 0; + + if (::GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0) { + return false; + } + + if (nDevices == 0) { + return false; + } + + std::vector rawInputDeviceList(nDevices); + if (::GetRawInputDeviceList(&rawInputDeviceList[0], &nDevices, sizeof(RAWINPUTDEVICELIST)) == static_cast(-1)) { + return false; + } + + for (unsigned int i = 0; i < nDevices; ++i) { + RID_DEVICE_INFO rdi = { sizeof(rdi) }; + unsigned int cbSize = sizeof(rdi); + + if (GetRawInputDeviceInfo(rawInputDeviceList[i].hDevice, RIDI_DEVICEINFO, &rdi, &cbSize) > 0) { + //skip non HID and non logitec (3DConnexion) devices + if (rdi.dwType != RIM_TYPEHID || rdi.hid.dwVendorId != LOGITECH_VENDOR_ID) { + continue; + } + + //check if devices matches Multi-axis Controller + for (unsigned int j = 0; j < numDevicesOfInterest; ++j) { + if (devicesToRegister[j].usUsage == rdi.hid.usUsage + && devicesToRegister[j].usUsagePage == rdi.hid.usUsagePage) { + return true; + } + } } - } + return false; } -void SpacemouseManager::destroy() { - QAbstractEventDispatcher::instance()->removeNativeEventFilter(this); - int deviceid = instance->getDeviceID(); - auto userInputMapper = DependencyManager::get(); - userInputMapper->removeDevice(deviceid); -} - -#define LOGITECH_VENDOR_ID 0x46d - -#ifndef RIDEV_DEVNOTIFY -#define RIDEV_DEVNOTIFY 0x00002000 -#endif - -const int TRACE_RIDI_DEVICENAME = 0; -const int TRACE_RIDI_DEVICEINFO = 0; - -#ifdef _WIN64 -typedef unsigned __int64 QWORD; -#endif - // object angular velocity per mouse tick 0.008 milliradians per second per count static const double k3dmouseAngularVelocity = 8.0e-6; // radians per second per count @@ -267,7 +328,6 @@ bool SpacemouseManager::RawInputEventFilter(void* msg, long* result) { auto userInputMapper = DependencyManager::get(); if (Is3dmouseAttached() && instance->getDeviceID() == controller::Input::INVALID_DEVICE) { userInputMapper->registerDevice(instance); - UserActivityLogger::getInstance().connectedDevice("controller", "Spacemouse"); } else if (!Is3dmouseAttached() && instance->getDeviceID() != controller::Input::INVALID_DEVICE) { userInputMapper->removeDevice(instance->getDeviceID()); @@ -290,20 +350,9 @@ bool SpacemouseManager::RawInputEventFilter(void* msg, long* result) { return false; } -// Access the mouse parameters structure -I3dMouseParam& SpacemouseManager::MouseParams() { - return f3dMouseParams; -} - -// Access the mouse parameters structure -const I3dMouseParam& SpacemouseManager::MouseParams() const { - return f3dMouseParams; -} - //Called with the processed motion data when a 3D mouse event is received void SpacemouseManager::Move3d(HANDLE device, std::vector& motionData) { Q_UNUSED(device); - instance->cc_position = { motionData[0] * 1000, motionData[1] * 1000, motionData[2] * 1000 }; instance->cc_rotation = { motionData[3] * 1500, motionData[4] * 1500, motionData[5] * 1500 }; instance->handleAxisEvent(); @@ -321,62 +370,6 @@ void SpacemouseManager::On3dmouseKeyUp(HANDLE device, int virtualKeyCode) { instance->setButton(0); } -//Get an initialized array of PRAWINPUTDEVICE for the 3D devices -//pNumDevices returns the number of devices to register. Currently this is always 1. -static PRAWINPUTDEVICE GetDevicesToRegister(unsigned int* pNumDevices) { - // Array of raw input devices to register - static RAWINPUTDEVICE sRawInputDevices[] = { - { 0x01, 0x08, 0x00, 0x00 } // Usage Page = 0x01 Generic Desktop Page, Usage Id= 0x08 Multi-axis Controller - }; - - if (pNumDevices) { - *pNumDevices = sizeof(sRawInputDevices) / sizeof(sRawInputDevices[0]); - } - - return sRawInputDevices; -} - -//Detect the 3D mouse -bool SpacemouseManager::Is3dmouseAttached() { - unsigned int numDevicesOfInterest = 0; - PRAWINPUTDEVICE devicesToRegister = GetDevicesToRegister(&numDevicesOfInterest); - - unsigned int nDevices = 0; - - if (::GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0) { - return false; - } - - if (nDevices == 0) { - return false; - } - - std::vector rawInputDeviceList(nDevices); - if (::GetRawInputDeviceList(&rawInputDeviceList[0], &nDevices, sizeof(RAWINPUTDEVICELIST)) == static_cast(-1)) { - return false; - } - - for (unsigned int i = 0; i < nDevices; ++i) { - RID_DEVICE_INFO rdi = { sizeof(rdi) }; - unsigned int cbSize = sizeof(rdi); - - if (GetRawInputDeviceInfo(rawInputDeviceList[i].hDevice, RIDI_DEVICEINFO, &rdi, &cbSize) > 0) { - //skip non HID and non logitec (3DConnexion) devices - if (rdi.dwType != RIM_TYPEHID || rdi.hid.dwVendorId != LOGITECH_VENDOR_ID) { - continue; - } - - //check if devices matches Multi-axis Controller - for (unsigned int j = 0; j < numDevicesOfInterest; ++j) { - if (devicesToRegister[j].usUsage == rdi.hid.usUsage - && devicesToRegister[j].usUsagePage == rdi.hid.usUsagePage) { - return true; - } - } - } - } - return false; -} // Initialize the window to recieve raw-input messages // This needs to be called initially so that Windows will send the messages from the 3D mouse to the window. @@ -861,7 +854,7 @@ void SpacemouseManager::init() { if (Is3dmouseAttached() && instance->getDeviceID() == controller::Input::INVALID_DEVICE) { auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(instance); - UserActivityLogger::getInstance().connectedDevice("controller", "Spacemouse"); + emit deviceConnected(getName()); } //let one axis be dominant //ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchDominant | kConnexionSwitchEnableAll, NULL); @@ -942,5 +935,3 @@ void MessageHandler(unsigned int connection, unsigned int messageType, void *mes } #endif // __APPLE__ - -#endif diff --git a/libraries/input-plugins/src/input-plugins/SpacemouseManager.h b/plugins/hifiSpacemouse/src/SpacemouseManager.h similarity index 77% rename from libraries/input-plugins/src/input-plugins/SpacemouseManager.h rename to plugins/hifiSpacemouse/src/SpacemouseManager.h index 82c4fa8fb6..a9933902e5 100644 --- a/libraries/input-plugins/src/input-plugins/SpacemouseManager.h +++ b/plugins/hifiSpacemouse/src/SpacemouseManager.h @@ -17,22 +17,8 @@ #include #include -#include "InputPlugin.h" +#include -#ifndef HAVE_3DCONNEXIONCLIENT -class SpacemouseManager : public QObject { - Q_OBJECT -public: - void ManagerFocusOutEvent(); - void init(); - void destroy() {}; - bool Is3dmouseAttached() { return false; }; - public slots: - void toggleSpacemouse(bool shouldEnable) {}; -}; -#endif - -#ifdef HAVE_3DCONNEXIONCLIENT // the windows connexion rawinput #ifdef Q_OS_WIN @@ -85,42 +71,26 @@ private: Speed fSpeed; }; -class SpacemouseManager : public QObject, public QAbstractNativeEventFilter { +class SpacemouseManager : public InputPlugin, public QAbstractNativeEventFilter { Q_OBJECT public: - SpacemouseManager() {}; + bool isSupported() const override; + const QString& getName() const override { return NAME; } + const QString& getID() const override { return NAME; } - void init(); - void destroy(); - bool Is3dmouseAttached(); + bool activate() override; + void deactivate() override; - SpacemouseManager* client; + void pluginFocusOutEvent() override; + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; - void ManagerFocusOutEvent(); - - I3dMouseParam& MouseParams(); - const I3dMouseParam& MouseParams() const; - - virtual void Move3d(HANDLE device, std::vector& motionData); - virtual void On3dmouseKeyDown(HANDLE device, int virtualKeyCode); - virtual void On3dmouseKeyUp(HANDLE device, int virtualKeyCode); - - virtual bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) Q_DECL_OVERRIDE - { - MSG* msg = static_cast< MSG * >(message); - return RawInputEventFilter(message, result); - } - - public slots: - void toggleSpacemouse(bool shouldEnable); - -signals: - void Move3d(std::vector& motionData); - void On3dmouseKeyDown(int virtualKeyCode); - void On3dmouseKeyUp(int virtualKeyCode); + bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override; private: + void Move3d(HANDLE device, std::vector& motionData); + void On3dmouseKeyDown(HANDLE device, int virtualKeyCode); + void On3dmouseKeyUp(HANDLE device, int virtualKeyCode); bool InitializeRawInput(HWND hwndTarget); bool RawInputEventFilter(void* msg, long* result); @@ -156,6 +126,9 @@ private: // use to calculate distance traveled since last event DWORD fLast3dmouseInputTime; + + static const QString NAME; + friend class SpacemouseDevice; }; // the osx connexion api @@ -176,8 +149,6 @@ public: #endif // __APPLE__ -#endif - // connnects to the userinputmapper class SpacemouseDevice : public QObject, public controller::InputDevice { @@ -214,7 +185,7 @@ public: virtual controller::Input::NamedVector getAvailableInputs() const override; virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void focusOutEvent() override; glm::vec3 cc_position; diff --git a/plugins/hifiSpacemouse/src/SpacemouseProvider.cpp b/plugins/hifiSpacemouse/src/SpacemouseProvider.cpp new file mode 100644 index 0000000000..c623f77d73 --- /dev/null +++ b/plugins/hifiSpacemouse/src/SpacemouseProvider.cpp @@ -0,0 +1,45 @@ +// +// Created by Bradley Austin Davis on 2015/10/25 +// 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 +#include + +#include "SpacemouseManager.h" + +class SpacemouseProvider : public QObject, public InputProvider +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID InputProvider_iid FILE "plugin.json") + Q_INTERFACES(InputProvider) + +public: + SpacemouseProvider(QObject* parent = nullptr) : QObject(parent) {} + virtual ~SpacemouseProvider() {} + + virtual InputPluginList getInputPlugins() override { + static std::once_flag once; + std::call_once(once, [&] { + InputPluginPointer plugin(new SpacemouseManager()); + if (plugin->isSupported()) { + _inputPlugins.push_back(plugin); + } + }); + return _inputPlugins; + } + +private: + InputPluginList _inputPlugins; +}; + +#include "SpacemouseProvider.moc" diff --git a/plugins/hifiSpacemouse/src/plugin.json b/plugins/hifiSpacemouse/src/plugin.json new file mode 100644 index 0000000000..294f436039 --- /dev/null +++ b/plugins/hifiSpacemouse/src/plugin.json @@ -0,0 +1 @@ +{"name":"Spacemouse"} diff --git a/plugins/oculus/CMakeLists.txt b/plugins/oculus/CMakeLists.txt index a91690ecdd..778be08dcf 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -13,7 +13,7 @@ if (WIN32) set(TARGET_NAME oculus) setup_hifi_plugin(Multimedia) - link_hifi_libraries(shared gl gpu controllers ui plugins display-plugins input-plugins audio-client networking) + link_hifi_libraries(shared gl gpu controllers ui plugins ui-plugins display-plugins input-plugins audio-client networking) include_hifi_library_headers(octree) diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index a92c5b5b22..e26a48b89c 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -8,22 +8,44 @@ #include "OculusBaseDisplayPlugin.h" #include +#include +#include #include "OculusHelpers.h" void OculusBaseDisplayPlugin::resetSensors() { ovr_RecenterTrackingOrigin(_session); + + _currentRenderFrameInfo.renderPose = glm::mat4(); // identity } -void OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { +bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo = FrameInfo(); _currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds();; _currentRenderFrameInfo.predictedDisplayTime = ovr_GetPredictedDisplayTime(_session, frameIndex); auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrTrue); _currentRenderFrameInfo.renderPose = toGlm(trackingState.HeadPose.ThePose); _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; - Lock lock(_mutex); - _frameInfos[frameIndex] = _currentRenderFrameInfo; + + std::array handPoses; + // Make controller poses available to the presentation thread + ovr_for_each_hand([&](ovrHandType hand) { + static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; + if (REQUIRED_HAND_STATUS != (trackingState.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { + return; + } + + auto correctedPose = ovrControllerPoseToHandPose(hand, trackingState.HandPoses[hand]); + static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y); + handPoses[hand] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION); + }); + + withRenderThreadLock([&] { + _uiModelTransform = DependencyManager::get()->getModelTransform(); + _handPoses = handPoses; + _frameInfos[frameIndex] = _currentRenderFrameInfo; + }); + return Parent::beginFrameRender(frameIndex); } bool OculusBaseDisplayPlugin::isSupported() const { diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index ed6f1a36b3..3e2c223908 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -16,11 +16,11 @@ class OculusBaseDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: - virtual bool isSupported() const override; + bool isSupported() const override; // Stereo specific methods - virtual void resetSensors() override final; - virtual void beginFrameRender(uint32_t frameIndex) override; + void resetSensors() override final; + bool beginFrameRender(uint32_t frameIndex) override; float getTargetFrameRate() const override { return _hmdDesc.DisplayRefreshRate; } diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 50ef6b09a1..b3b1b20b2b 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -13,7 +13,7 @@ #include -#include +#include #include #include @@ -76,12 +76,12 @@ void OculusControllerManager::deactivate() { } } -void OculusControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void OculusControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { PerformanceTimer perfTimer("OculusControllerManager::TouchDevice::update"); if (_touch) { if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) { - _touch->update(deltaTime, inputCalibrationData, jointsCaptured); + _touch->update(deltaTime, inputCalibrationData); } else { qCWarning(oculus) << "Unable to read Oculus touch input state"; } @@ -89,7 +89,7 @@ void OculusControllerManager::pluginUpdate(float deltaTime, const controller::In if (_remote) { if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Remote, &_inputState))) { - _remote->update(deltaTime, inputCalibrationData, jointsCaptured); + _remote->update(deltaTime, inputCalibrationData); } else { qCWarning(oculus) << "Unable to read Oculus remote input state"; } @@ -105,6 +105,12 @@ void OculusControllerManager::pluginFocusOutEvent() { } } +void OculusControllerManager::stopHapticPulse(bool leftHand) { + if (_touch) { + _touch->stopHapticPulse(leftHand); + } +} + using namespace controller; static const std::vector> BUTTON_MAP { { @@ -120,14 +126,14 @@ static const std::vector> BUTTON_MAP { ovrButton_B, B }, { ovrButton_LThumb, LS }, { ovrButton_RThumb, RS }, - { ovrButton_LShoulder, LB }, - { ovrButton_RShoulder, RB }, + //{ ovrButton_LShoulder, LB }, + //{ ovrButton_RShoulder, RB }, } }; static const std::vector> TOUCH_MAP { { - { ovrTouch_X, LEFT_SECONDARY_THUMB_TOUCH }, + { ovrTouch_X, LEFT_PRIMARY_THUMB_TOUCH }, { ovrTouch_Y, LEFT_SECONDARY_THUMB_TOUCH }, - { ovrTouch_A, RIGHT_SECONDARY_THUMB_TOUCH }, + { ovrTouch_A, RIGHT_PRIMARY_THUMB_TOUCH }, { ovrTouch_B, RIGHT_SECONDARY_THUMB_TOUCH }, { ovrTouch_LIndexTrigger, LEFT_PRIMARY_INDEX_TOUCH }, { ovrTouch_RIndexTrigger, RIGHT_PRIMARY_INDEX_TOUCH }, @@ -158,7 +164,7 @@ QString OculusControllerManager::RemoteDevice::getDefaultMappingConfig() const { return MAPPING_JSON; } -void OculusControllerManager::RemoteDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void OculusControllerManager::RemoteDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _buttonPressedMap.clear(); const auto& inputState = _parent._inputState; for (const auto& pair : BUTTON_MAP) { @@ -172,33 +178,33 @@ void OculusControllerManager::RemoteDevice::focusOutEvent() { _buttonPressedMap.clear(); } -void OculusControllerManager::TouchDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void OculusControllerManager::TouchDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _poseStateMap.clear(); _buttonPressedMap.clear(); - if (!jointsCaptured) { - int numTrackedControllers = 0; - static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; - auto tracking = ovr_GetTrackingState(_parent._session, 0, false); - ovr_for_each_hand([&](ovrHandType hand) { - ++numTrackedControllers; - if (REQUIRED_HAND_STATUS == (tracking.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { - handlePose(deltaTime, inputCalibrationData, hand, tracking.HandPoses[hand]); - } - }); - } + int numTrackedControllers = 0; + static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; + auto tracking = ovr_GetTrackingState(_parent._session, 0, false); + ovr_for_each_hand([&](ovrHandType hand) { + ++numTrackedControllers; + if (REQUIRED_HAND_STATUS == (tracking.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { + handlePose(deltaTime, inputCalibrationData, hand, tracking.HandPoses[hand]); + } else { + _poseStateMap[hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND].valid = false; + } + }); using namespace controller; // Axes const auto& inputState = _parent._inputState; _axisStateMap[LX] = inputState.Thumbstick[ovrHand_Left].x; _axisStateMap[LY] = inputState.Thumbstick[ovrHand_Left].y; _axisStateMap[LT] = inputState.IndexTrigger[ovrHand_Left]; - _axisStateMap[LG] = inputState.HandTrigger[ovrHand_Left]; + _axisStateMap[LEFT_GRIP] = inputState.HandTrigger[ovrHand_Left]; _axisStateMap[RX] = inputState.Thumbstick[ovrHand_Right].x; _axisStateMap[RY] = inputState.Thumbstick[ovrHand_Right].y; _axisStateMap[RT] = inputState.IndexTrigger[ovrHand_Right]; - _axisStateMap[RG] = inputState.HandTrigger[ovrHand_Right]; + _axisStateMap[RIGHT_GRIP] = inputState.HandTrigger[ovrHand_Right]; // Buttons for (const auto& pair : BUTTON_MAP) { @@ -212,6 +218,21 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control _buttonPressedMap.insert(pair.second); } } + + // Haptics + { + Locker locker(_lock); + if (_leftHapticDuration > 0.0f) { + _leftHapticDuration -= deltaTime * 1000.0f; // milliseconds + } else { + stopHapticPulse(true); + } + if (_rightHapticDuration > 0.0f) { + _rightHapticDuration -= deltaTime * 1000.0f; // milliseconds + } else { + stopHapticPulse(false); + } + } } void OculusControllerManager::TouchDevice::focusOutEvent() { @@ -224,36 +245,97 @@ void OculusControllerManager::TouchDevice::handlePose(float deltaTime, const ovrPoseStatef& handPose) { auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND; auto& pose = _poseStateMap[poseId]; - pose.translation = toGlm(handPose.ThePose.Position); - pose.rotation = toGlm(handPose.ThePose.Orientation); - pose.angularVelocity = toGlm(handPose.AngularVelocity); - pose.velocity = toGlm(handPose.LinearVelocity); + pose = ovrControllerPoseToHandPose(hand, handPose); + // transform into avatar frame + glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; + pose = pose.transform(controllerToAvatar); + +} + +bool OculusControllerManager::TouchDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) { + Locker locker(_lock); + bool toReturn = true; + if (hand == controller::BOTH || hand == controller::LEFT) { + if (strength == 0.0f) { + _leftHapticStrength = 0.0f; + _leftHapticDuration = 0.0f; + } else { + _leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength; + if (ovr_SetControllerVibration(_parent._session, ovrControllerType_LTouch, 1.0f, _leftHapticStrength) != ovrSuccess) { + toReturn = false; + } + _leftHapticDuration = std::max(duration, _leftHapticDuration); + } + } + if (hand == controller::BOTH || hand == controller::RIGHT) { + if (strength == 0.0f) { + _rightHapticStrength = 0.0f; + _rightHapticDuration = 0.0f; + } else { + _rightHapticStrength = (duration > _rightHapticDuration) ? strength : _rightHapticStrength; + if (ovr_SetControllerVibration(_parent._session, ovrControllerType_RTouch, 1.0f, _rightHapticStrength) != ovrSuccess) { + toReturn = false; + } + _rightHapticDuration = std::max(duration, _rightHapticDuration); + } + } + return toReturn; +} + +void OculusControllerManager::TouchDevice::stopHapticPulse(bool leftHand) { + auto handType = (leftHand ? ovrControllerType_LTouch : ovrControllerType_RTouch); + ovr_SetControllerVibration(_parent._session, handType, 0.0f, 0.0f); } controller::Input::NamedVector OculusControllerManager::TouchDevice::getAvailableInputs() const { using namespace controller; QVector availableInputs{ - // Trackpad analogs + // buttons + makePair(A, "A"), + makePair(B, "B"), + makePair(X, "X"), + makePair(Y, "Y"), + + // trackpad analogs makePair(LX, "LX"), makePair(LY, "LY"), makePair(RX, "RX"), makePair(RY, "RY"), - // trigger analogs + + // triggers makePair(LT, "LT"), makePair(RT, "RT"), - makePair(LB, "LB"), - makePair(RB, "RB"), + // trigger buttons + //makePair(LB, "LB"), + //makePair(RB, "RB"), + // side grip triggers + makePair(LEFT_GRIP, "LeftGrip"), + makePair(RIGHT_GRIP, "RightGrip"), + + // joystick buttons makePair(LS, "LS"), makePair(RS, "RS"), + makePair(LEFT_HAND, "LeftHand"), makePair(RIGHT_HAND, "RightHand"), - makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), - makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"), - makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"), - makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"), + makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"), + makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"), + makePair(RIGHT_PRIMARY_THUMB_TOUCH, "RightPrimaryThumbTouch"), + makePair(RIGHT_SECONDARY_THUMB_TOUCH, "RightSecondaryThumbTouch"), + makePair(LEFT_PRIMARY_INDEX_TOUCH, "LeftPrimaryIndexTouch"), + makePair(RIGHT_PRIMARY_INDEX_TOUCH, "RightPrimaryIndexTouch"), + makePair(LS_TOUCH, "LSTouch"), + makePair(RS_TOUCH, "RSTouch"), + makePair(LEFT_THUMB_UP, "LeftThumbUp"), + makePair(RIGHT_THUMB_UP, "RightThumbUp"), + makePair(LEFT_INDEX_POINT, "LeftIndexPoint"), + makePair(RIGHT_INDEX_POINT, "RightIndexPoint"), + + makePair(BACK, "LeftApplicationMenu"), + makePair(START, "RightApplicationMenu"), }; return availableInputs; } diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index 60969097f8..3c5cdeb7c6 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -24,14 +24,16 @@ class OculusControllerManager : public InputPlugin { public: // Plugin functions bool isSupported() const override; - bool isJointController() const override { return true; } const QString& getName() const override { return NAME; } bool activate() override; void deactivate() override; void pluginFocusOutEvent() override; - void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + +private slots: + void stopHapticPulse(bool leftHand); private: class OculusInputDevice : public controller::InputDevice { @@ -49,7 +51,7 @@ private: controller::Input::NamedVector getAvailableInputs() const override; QString getDefaultMappingConfig() const override; - void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void focusOutEvent() override; friend class OculusControllerManager; @@ -62,12 +64,27 @@ private: controller::Input::NamedVector getAvailableInputs() const override; QString getDefaultMappingConfig() const override; - void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void focusOutEvent() override; + bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; + private: + void stopHapticPulse(bool leftHand); void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose); int _trackedControllers { 0 }; + + // perform an action when the TouchDevice mutex is acquired. + using Locker = std::unique_lock; + template + void withLock(F&& f) { Locker locker(_lock); f(); } + + float _leftHapticDuration { 0.0f }; + float _leftHapticStrength { 0.0f }; + float _rightHapticDuration { 0.0f }; + float _rightHapticStrength { 0.0f }; + mutable std::recursive_mutex _lock; + friend class OculusControllerManager; }; diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 1006d69f06..2b2ec5bdb0 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -28,6 +28,12 @@ bool OculusDisplayPlugin::internalActivate() { return result; } +void OculusDisplayPlugin::init() { + Plugin::init(); + + emit deviceConnected(getName()); +} + void OculusDisplayPlugin::cycleDebugOutput() { if (_session) { currentDebugMode = static_cast((currentDebugMode + 1) % ovrPerfHud_Count); diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index d6cd6f6f3d..ed6e0d13ea 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -17,6 +17,8 @@ class OculusDisplayPlugin : public OculusBaseDisplayPlugin { public: const QString& getName() const override { return NAME; } + void init() override; + QString getPreferredAudioInDevice() const override; QString getPreferredAudioOutDevice() const override; diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 6ddace684b..49c14c8d66 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -14,6 +14,10 @@ #include #include #include +#include + +#include +#include using Mutex = std::mutex; using Lock = std::unique_lock; @@ -50,6 +54,13 @@ bool oculusAvailable() { static std::once_flag once; static bool result { false }; std::call_once(once, [&] { + + static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); + static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + if (enableDebugOpenVR) { + return; + } + ovrDetectResult detect = ovr_Detect(0); if (!detect.IsOculusServiceRunning || !detect.IsOculusHMDConnected) { return; @@ -191,3 +202,88 @@ void SwapFramebufferWrapper::onBind(oglplus::Framebuffer::Target target) { void SwapFramebufferWrapper::onUnbind(oglplus::Framebuffer::Target target) { glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); } + + +controller::Pose ovrControllerPoseToHandPose( + ovrHandType hand, + const ovrPoseStatef& handPose) { + // When the sensor-to-world rotation is identity the coordinate axes look like this: + // + // user + // forward + // -z + // | + // y| user + // y o----x right + // o-----x user + // | up + // | + // z + // + // Rift + + // From ABOVE the hand canonical axes looks like this: + // + // | | | | y | | | | + // | | | | | | | | | + // | | | | | + // |left | / x---- + \ |right| + // | _/ z \_ | + // | | | | + // | | | | + // + + // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down + // the rotation to align the Touch axes with those of the hands is: + // + // touchToHand = halfTurnAboutY * quaterTurnAboutX + + // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. + // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that + // the combination (measurement * offset) is identity at this orientation. + // + // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) + // + // An approximate offset for the Touch can be obtained by inspection: + // + // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) + // + // So the full equation is: + // + // Q = combinedMeasurement * touchToHand + // + // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) + // + // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) + 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 touchToHand = yFlip * quarterX; + + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuarterZ = 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(leftQuarterZ * eighthX) * touchToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand; + + 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 glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; + static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; + + auto translationOffset = (hand == ovrHand_Left ? leftTranslationOffset : rightTranslationOffset); + auto rotationOffset = (hand == ovrHand_Left ? leftRotationOffset : rightRotationOffset); + + glm::quat rotation = toGlm(handPose.ThePose.Orientation); + + controller::Pose pose; + pose.translation = toGlm(handPose.ThePose.Position); + pose.translation += rotation * translationOffset; + pose.rotation = rotation * rotationOffset; + pose.angularVelocity = toGlm(handPose.AngularVelocity); + pose.velocity = toGlm(handPose.LinearVelocity); + pose.valid = true; + return pose; +} \ No newline at end of file diff --git a/plugins/oculus/src/OculusHelpers.h b/plugins/oculus/src/OculusHelpers.h index 2f13c45466..66cdccf15a 100644 --- a/plugins/oculus/src/OculusHelpers.h +++ b/plugins/oculus/src/OculusHelpers.h @@ -13,6 +13,7 @@ #include #include +#include void logWarning(const char* what); void logFatal(const char* what); @@ -128,3 +129,7 @@ protected: private: ovrSession _session; }; + +controller::Pose ovrControllerPoseToHandPose( + ovrHandType hand, + const ovrPoseStatef& handPose); diff --git a/plugins/oculus/src/oculus.json b/plugins/oculus/src/oculus.json index 0967ef424b..86546c8dd5 100644 --- a/plugins/oculus/src/oculus.json +++ b/plugins/oculus/src/oculus.json @@ -1 +1 @@ -{} +{"name":"Oculus Rift"} diff --git a/plugins/oculusLegacy/CMakeLists.txt b/plugins/oculusLegacy/CMakeLists.txt index a4e00013f1..c1f2c6249f 100644 --- a/plugins/oculusLegacy/CMakeLists.txt +++ b/plugins/oculusLegacy/CMakeLists.txt @@ -12,7 +12,7 @@ if (APPLE) set(TARGET_NAME oculusLegacy) setup_hifi_plugin() - link_hifi_libraries(shared gl gpu plugins ui display-plugins input-plugins) + link_hifi_libraries(shared gl gpu plugins ui ui-plugins display-plugins input-plugins) include_hifi_library_headers(octree) diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 753ff923dd..6da842b7b9 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -26,27 +26,36 @@ #include #include -#include "plugins/PluginContainer.h" +#include #include "OculusHelpers.h" using namespace oglplus; -const QString OculusLegacyDisplayPlugin::NAME("Oculus Rift (0.5) (Legacy)"); +const QString OculusLegacyDisplayPlugin::NAME("Oculus Rift"); OculusLegacyDisplayPlugin::OculusLegacyDisplayPlugin() { } +void OculusLegacyDisplayPlugin::init() { + Plugin::init(); + + emit deviceConnected(getName()); +} + void OculusLegacyDisplayPlugin::resetSensors() { ovrHmd_RecenterPose(_hmd); } -void OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { +bool OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + _currentRenderFrameInfo = FrameInfo(); _currentRenderFrameInfo.predictedDisplayTime = _currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds(); _trackingState = ovrHmd_GetTrackingState(_hmd, _currentRenderFrameInfo.predictedDisplayTime); _currentRenderFrameInfo.rawRenderPose = _currentRenderFrameInfo.renderPose = toGlm(_trackingState.HeadPose.ThePose); - Lock lock(_mutex); - _frameInfos[frameIndex] = _currentRenderFrameInfo; + withRenderThreadLock([&]{ + _frameInfos[frameIndex] = _currentRenderFrameInfo; + }); + return Parent::beginFrameRender(frameIndex); } bool OculusLegacyDisplayPlugin::isSupported() const { @@ -59,18 +68,48 @@ bool OculusLegacyDisplayPlugin::isSupported() const { } auto hmd = ovrHmd_Create(0); + + // The Oculus SDK seems to have trouble finding the right screen sometimes, so we have to guess + // Guesses, in order of best match: + // - resolution and position match + // - resolution and one component of position match + // - resolution matches + // - position matches + // If it still picks the wrong screen, you'll have to mess with your monitor configuration + QList matches({ -1, -1, -1, -1 }); if (hmd) { QPoint targetPosition{ hmd->WindowsPos.x, hmd->WindowsPos.y }; + QSize targetResolution{ hmd->Resolution.w, hmd->Resolution.h }; auto screens = qApp->screens(); for(int i = 0; i < screens.size(); ++i) { auto screen = screens[i]; QPoint position = screen->geometry().topLeft(); - if (position == targetPosition) { - _hmdScreen = i; - break; + QSize resolution = screen->geometry().size(); + + if (position == targetPosition && resolution == targetResolution) { + matches[0] = i; + } else if ((position.x() == targetPosition.x() || position.y() == targetPosition.y()) && + resolution == targetResolution) { + matches[1] = i; + } else if (resolution == targetResolution) { + matches[2] = i; + } else if (position == targetPosition) { + matches[3] = i; } } } + + for (int screen : matches) { + if (screen != -1) { + _hmdScreen = screen; + break; + } + } + + if (_hmdScreen == -1) { + qDebug() << "Could not find Rift screen"; + result = false; + } ovr_Shutdown(); return result; diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index 5900ad4c58..6ffc1a7f44 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -23,11 +23,13 @@ public: bool isSupported() const override; const QString& getName() const override { return NAME; } + void init() override; + int getHmdScreen() const override; // Stereo specific methods void resetSensors() override; - void beginFrameRender(uint32_t frameIndex) override; + bool beginFrameRender(uint32_t frameIndex) override; float getTargetFrameRate() const override; diff --git a/plugins/oculusLegacy/src/oculus.json b/plugins/oculusLegacy/src/oculus.json index 0967ef424b..86546c8dd5 100644 --- a/plugins/oculusLegacy/src/oculus.json +++ b/plugins/oculusLegacy/src/oculus.json @@ -1 +1 @@ -{} +{"name":"Oculus Rift"} diff --git a/plugins/openvr/CMakeLists.txt b/plugins/openvr/CMakeLists.txt index 1ba8d05b92..8263e87767 100644 --- a/plugins/openvr/CMakeLists.txt +++ b/plugins/openvr/CMakeLists.txt @@ -12,7 +12,7 @@ if (WIN32) set(TARGET_NAME openvr) setup_hifi_plugin(OpenGL Script Qml Widgets) link_hifi_libraries(shared gl networking controllers ui - plugins display-plugins input-plugins script-engine + plugins display-plugins ui-plugins input-plugins script-engine render-utils model gpu render model-networking fbx) include_hifi_library_headers(octree) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 38719fdca5..cfb374e3bd 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -9,7 +9,6 @@ #include -#include #include #include #include @@ -19,9 +18,11 @@ #include #include +#include #include -#include +#include #include +#include #include #include "OpenVrHelpers.h" @@ -30,23 +31,29 @@ Q_DECLARE_LOGGING_CATEGORY(displayplugins) const QString OpenVrDisplayPlugin::NAME("OpenVR (Vive)"); const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; // this probably shouldn't be hardcoded here -static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); -static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); - - -static vr::IVRCompositor* _compositor{ nullptr }; +static vr::IVRCompositor* _compositor { nullptr }; vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; + mat4 _trackedDevicePoseMat4[vr::k_unMaxTrackedDeviceCount]; vec3 _trackedDeviceLinearVelocities[vr::k_unMaxTrackedDeviceCount]; vec3 _trackedDeviceAngularVelocities[vr::k_unMaxTrackedDeviceCount]; + static mat4 _sensorResetMat; static std::array VR_EYES { { vr::Eye_Left, vr::Eye_Right } }; +bool _openVrDisplayActive { false }; bool OpenVrDisplayPlugin::isSupported() const { - return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent(); + return openVrSupported(); +} + +void OpenVrDisplayPlugin::init() { + Plugin::init(); + + emit deviceConnected(getName()); } bool OpenVrDisplayPlugin::internalActivate() { + _openVrDisplayActive = true; _container->setIsOptionChecked(StandingHMDSensorMode, true); if (!_system) { @@ -62,16 +69,14 @@ bool OpenVrDisplayPlugin::internalActivate() { // left + right eyes _renderTargetSize.x *= 2; - { - Lock lock(_poseMutex); + withRenderThreadLock([&] { openvr_for_each_eye([&](vr::Hmd_Eye eye) { _eyeOffsets[eye] = toGlm(_system->GetEyeToHeadTransform(eye)); _eyeProjections[eye] = toGlm(_system->GetProjectionMatrix(eye, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, vr::API_OpenGL)); }); // FIXME Calculate the proper combined projection by using GetProjectionRaw values from both eyes _cullingProjection = _eyeProjections[0]; - - } + }); _compositor = vr::VRCompositor(); Q_ASSERT(_compositor); @@ -91,7 +96,9 @@ bool OpenVrDisplayPlugin::internalActivate() { glm::vec3 uiPos(0.0f, UI_HEIGHT, UI_RADIUS - (0.5f * zSize) - UI_Z_OFFSET); _sensorResetMat = glm::inverse(createMatFromQuatAndPos(glm::quat(), uiPos)); } else { - qDebug() << "OpenVR: error could not get chaperone pointer"; + #if DEV_BUILD + qDebug() << "OpenVR: error could not get chaperone pointer"; + #endif } return Parent::internalActivate(); @@ -99,8 +106,14 @@ bool OpenVrDisplayPlugin::internalActivate() { void OpenVrDisplayPlugin::internalDeactivate() { Parent::internalDeactivate(); + _openVrDisplayActive = false; _container->setIsOptionChecked(StandingHMDSensorMode, false); if (_system) { + // Invalidate poses. It's fine if someone else sets these shared values, but we're about to stop updating them, and + // we don't want ViveControllerManager to consider old values to be valid. + for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { + _trackedDevicePose[i].bPoseIsValid = false; + } releaseOpenVrSystem(); _system = nullptr; } @@ -110,7 +123,7 @@ void OpenVrDisplayPlugin::internalDeactivate() { void OpenVrDisplayPlugin::customizeContext() { // Display plugins in DLLs must initialize glew locally static std::once_flag once; - std::call_once(once, []{ + std::call_once(once, [] { glewExperimental = true; GLenum err = glewInit(); glGetError(); // clear the potential error from glewExperimental @@ -120,13 +133,16 @@ void OpenVrDisplayPlugin::customizeContext() { } void OpenVrDisplayPlugin::resetSensors() { - Lock lock(_poseMutex); - glm::mat4 m = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); - _sensorResetMat = glm::inverse(cancelOutRollAndPitch(m)); + withRenderThreadLock([&] { + glm::mat4 m = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); + _sensorResetMat = glm::inverse(cancelOutRollAndPitch(m)); + }); } -void OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { +bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + handleOpenVrEvents(); + double displayFrequency = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float); double frameDuration = 1.f / displayFrequency; double vsyncToPhotons = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float); @@ -142,6 +158,24 @@ void OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, _currentRenderFrameInfo.predictedDisplayTime, _trackedDevicePose, vr::k_unMaxTrackedDeviceCount); + + vr::TrackedDeviceIndex_t handIndices[2] { vr::k_unTrackedDeviceIndexInvalid, vr::k_unTrackedDeviceIndexInvalid }; + { + vr::TrackedDeviceIndex_t controllerIndices[2] ; + auto trackedCount = _system->GetSortedTrackedDeviceIndicesOfClass(vr::TrackedDeviceClass_Controller, controllerIndices, 2); + // Find the left and right hand controllers, if they exist + for (uint32_t i = 0; i < std::min(trackedCount, 2); ++i) { + if (_trackedDevicePose[i].bPoseIsValid) { + auto role = _system->GetControllerRoleForTrackedDeviceIndex(controllerIndices[i]); + if (vr::TrackedControllerRole_LeftHand == role) { + handIndices[0] = controllerIndices[i]; + } else if (vr::TrackedControllerRole_RightHand == role) { + handIndices[1] = controllerIndices[i]; + } + } + } + } + // copy and process predictedTrackedDevicePoses for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { _trackedDevicePoseMat4[i] = _sensorResetMat * toGlm(_trackedDevicePose[i].mDeviceToAbsoluteTracking); @@ -151,17 +185,39 @@ void OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.rawRenderPose = toGlm(_trackedDevicePose[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking); _currentRenderFrameInfo.renderPose = _trackedDevicePoseMat4[vr::k_unTrackedDeviceIndex_Hmd]; - Lock lock(_mutex); - _frameInfos[frameIndex] = _currentRenderFrameInfo; + bool keyboardVisible = isOpenVrKeyboardShown(); + + std::array handPoses; + if (!keyboardVisible) { + for (int i = 0; i < 2; ++i) { + if (handIndices[i] == vr::k_unTrackedDeviceIndexInvalid) { + continue; + } + auto deviceIndex = handIndices[i]; + const mat4& mat = _trackedDevicePoseMat4[deviceIndex]; + const vec3& linearVelocity = _trackedDeviceLinearVelocities[deviceIndex]; + const vec3& angularVelocity = _trackedDeviceAngularVelocities[deviceIndex]; + auto correctedPose = openVrControllerPoseToHandPose(i == 0, mat, linearVelocity, angularVelocity); + static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y); + handPoses[i] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION); + } + } + + withRenderThreadLock([&] { + _uiModelTransform = DependencyManager::get()->getModelTransform(); + // Make controller poses available to the presentation thread + _handPoses = handPoses; + _frameInfos[frameIndex] = _currentRenderFrameInfo; + }); + return Parent::beginFrameRender(frameIndex); } void OpenVrDisplayPlugin::hmdPresent() { - PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentPresentFrameIndex) // Flip y-axis since GL UV coords are backwards. - static vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 }; - static vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 }; + static vr::VRTextureBounds_t leftBounds { 0, 0, 0.5f, 1 }; + static vr::VRTextureBounds_t rightBounds { 0.5f, 0, 1, 1 }; vr::Texture_t texture { (void*)oglplus::GetName(_compositeFramebuffer->color), vr::API_OpenGL, vr::ColorSpace_Auto }; @@ -182,6 +238,10 @@ bool OpenVrDisplayPlugin::isHmdMounted() const { } void OpenVrDisplayPlugin::updatePresentPose() { + mat4 sensorResetMat; + withPresentThreadLock([&] { + sensorResetMat = _sensorResetMat; + }); { float fSecondsSinceLastVsync; _system->GetTimeSinceLastVsync(&fSecondsSinceLastVsync, nullptr); @@ -193,9 +253,32 @@ void OpenVrDisplayPlugin::updatePresentPose() { _system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, fPredictedSecondsFromNow, &pose, 1); _currentPresentFrameInfo.rawPresentPose = toGlm(pose.mDeviceToAbsoluteTracking); } - _currentPresentFrameInfo.presentPose = _sensorResetMat * _currentPresentFrameInfo.rawPresentPose; + _currentPresentFrameInfo.presentPose = sensorResetMat * _currentPresentFrameInfo.rawPresentPose; mat3 renderRotation(_currentPresentFrameInfo.rawRenderPose); mat3 presentRotation(_currentPresentFrameInfo.rawPresentPose); _currentPresentFrameInfo.presentReprojection = glm::mat3(glm::inverse(renderRotation) * presentRotation); } +bool OpenVrDisplayPlugin::suppressKeyboard() { + if (isOpenVrKeyboardShown()) { + return false; + } + if (!_keyboardSupressionCount.fetch_add(1)) { + disableOpenVrKeyboard(); + } + return true; +} + +void OpenVrDisplayPlugin::unsuppressKeyboard() { + if (_keyboardSupressionCount == 0) { + qWarning() << "Attempted to unsuppress a keyboard that was not suppressed"; + return; + } + if (1 == _keyboardSupressionCount.fetch_sub(1)) { + enableOpenVrKeyboard(_container); + } +} + +bool OpenVrDisplayPlugin::isKeyboardVisible() { + return isOpenVrKeyboardShown(); +} diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 75193c5c98..fca4dab9e9 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -18,18 +18,24 @@ const float TARGET_RATE_OpenVr = 90.0f; // FIXME: get from sdk tracked device p class OpenVrDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: - virtual bool isSupported() const override; - virtual const QString& getName() const override { return NAME; } + bool isSupported() const override; + const QString& getName() const override { return NAME; } - virtual float getTargetFrameRate() const override { return TARGET_RATE_OpenVr; } + void init() override; - virtual void customizeContext() override; + float getTargetFrameRate() const override { return TARGET_RATE_OpenVr; } + + void customizeContext() override; // Stereo specific methods - virtual void resetSensors() override; - virtual void beginFrameRender(uint32_t frameIndex) override; + void resetSensors() override; + bool beginFrameRender(uint32_t frameIndex) override; void cycleDebugOutput() override { _lockCurrentTexture = !_lockCurrentTexture; } + bool suppressKeyboard() override; + void unsuppressKeyboard() override; + bool isKeyboardVisible() override; + protected: bool internalActivate() override; void internalDeactivate() override; @@ -39,9 +45,10 @@ protected: bool isHmdMounted() const override; void postPreview() override; + private: vr::IVRSystem* _system { nullptr }; std::atomic _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown }; + std::atomic _keyboardSupressionCount{ 0 }; static const QString NAME; - mutable Mutex _poseMutex; }; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 8ddf028dd2..c93a2178b5 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -13,8 +13,17 @@ #include #include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include "../../interface/src/Menu.h" Q_DECLARE_LOGGING_CATEGORY(displayplugins) Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") @@ -44,22 +53,36 @@ bool isOculusPresent() { return result; } +bool openVrSupported() { + static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); + static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent(); +} + vr::IVRSystem* acquireOpenVrSystem() { bool hmdPresent = vr::VR_IsHmdPresent(); if (hmdPresent) { Lock lock(mutex); if (!activeHmd) { - qCDebug(displayplugins) << "openvr: No vr::IVRSystem instance active, building"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: No vr::IVRSystem instance active, building"; + #endif vr::EVRInitError eError = vr::VRInitError_None; activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene); - qCDebug(displayplugins) << "openvr display: HMD is " << activeHmd << " error is " << eError; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError; + #endif } if (activeHmd) { - qCDebug(displayplugins) << "openvr: incrementing refcount"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: incrementing refcount"; + #endif ++refCount; } } else { - qCDebug(displayplugins) << "openvr: no hmd present"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: no hmd present"; + #endif } return activeHmd; } @@ -67,12 +90,277 @@ vr::IVRSystem* acquireOpenVrSystem() { void releaseOpenVrSystem() { if (activeHmd) { Lock lock(mutex); - qCDebug(displayplugins) << "openvr: decrementing refcount"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: decrementing refcount"; + #endif --refCount; if (0 == refCount) { - qCDebug(displayplugins) << "openvr: zero refcount, deallocate VR system"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: zero refcount, deallocate VR system"; + #endif vr::VR_Shutdown(); activeHmd = nullptr; } } } + +static char textArray[8192]; + +static QMetaObject::Connection _focusConnection, _focusTextConnection, _overlayMenuConnection; +extern bool _openVrDisplayActive; +static vr::IVROverlay* _overlay { nullptr }; +static QObject* _keyboardFocusObject { nullptr }; +static QString _existingText; +static Qt::InputMethodHints _currentHints; +extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; +static bool _keyboardShown { false }; +static bool _overlayRevealed { false }; +static const uint32_t SHOW_KEYBOARD_DELAY_MS = 400; + +void showOpenVrKeyboard(bool show = true) { + if (!_overlay) { + return; + } + + if (show) { + // To avoid flickering the keyboard when a text element is only briefly selected, + // show the keyboard asynchrnously after a very short delay, but only after we check + // that the current focus object is still one that is text enabled + QTimer::singleShot(SHOW_KEYBOARD_DELAY_MS, [] { + auto offscreenUi = DependencyManager::get(); + auto currentFocus = offscreenUi->getWindow()->focusObject(); + QInputMethodQueryEvent query(Qt::ImEnabled | Qt::ImQueryInput | Qt::ImHints); + qApp->sendEvent(currentFocus, &query); + // Current focus isn't text enabled, bail early. + if (!query.value(Qt::ImEnabled).toBool()) { + return; + } + // We're going to show the keyboard now... + _keyboardFocusObject = currentFocus; + _currentHints = Qt::InputMethodHints(query.value(Qt::ImHints).toUInt()); + vr::EGamepadTextInputMode inputMode = vr::k_EGamepadTextInputModeNormal; + if (_currentHints & Qt::ImhHiddenText) { + inputMode = vr::k_EGamepadTextInputModePassword; + } + vr::EGamepadTextInputLineMode lineMode = vr::k_EGamepadTextInputLineModeSingleLine; + if (_currentHints & Qt::ImhMultiLine) { + lineMode = vr::k_EGamepadTextInputLineModeMultipleLines; + } + _existingText = query.value(Qt::ImSurroundingText).toString(); + + auto showKeyboardResult = _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, + _existingText.toLocal8Bit().toStdString().c_str(), false, 0); + + if (vr::VROverlayError_None == showKeyboardResult) { + _keyboardShown = true; + // Try to position the keyboard slightly below where the user is looking. + mat4 headPose = cancelOutRollAndPitch(toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking)); + mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1)); + keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0)); + auto keyboardTransformVr = toOpenVr(keyboardTransform); + _overlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &keyboardTransformVr); + } + }); + } else { + _keyboardFocusObject = nullptr; + if (_keyboardShown) { + _overlay->HideKeyboard(); + _keyboardShown = false; + } + } +} + +void finishOpenVrKeyboardInput() { + auto offscreenUi = DependencyManager::get(); + auto chars = _overlay->GetKeyboardText(textArray, 8192); + auto newText = QString(QByteArray(textArray, chars)); + _keyboardFocusObject->setProperty("text", newText); + //// TODO modify the new text to match the possible input hints: + //// ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly + //// ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly + //QInputMethodEvent event(_existingText, QList()); + //event.setCommitString(newText, 0, _existingText.size()); + //qApp->sendEvent(_keyboardFocusObject, &event); + // Simulate an enter press on the top level window to trigger the action + if (0 == (_currentHints & Qt::ImhMultiLine)) { + qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n"))); + qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyRelease, Qt::Key_Return, Qt::KeyboardModifiers())); + } +} + +static const QString DEBUG_FLAG("HIFI_DISABLE_STEAM_VR_KEYBOARD"); +bool disableSteamVrKeyboard = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + +void enableOpenVrKeyboard(PluginContainer* container) { + if (disableSteamVrKeyboard) { + return; + } + auto offscreenUi = DependencyManager::get(); + _overlay = vr::VROverlay(); + + + auto menu = container->getPrimaryMenu(); + auto action = menu->getActionForOption(MenuOption::Overlays); + + // When the overlays are revealed, suppress the keyboard from appearing on text focus for a tenth of a second. + _overlayMenuConnection = QObject::connect(action, &QAction::triggered, [action] { + if (action->isChecked()) { + _overlayRevealed = true; + const int KEYBOARD_DELAY_MS = 100; + QTimer::singleShot(KEYBOARD_DELAY_MS, [&] { _overlayRevealed = false; }); + } + }); + + _focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) { + if (object != _keyboardFocusObject) { + showOpenVrKeyboard(false); + } + }); + + _focusTextConnection = QObject::connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [](bool focusText) { + if (_openVrDisplayActive) { + if (_overlayRevealed) { + // suppress at most one text focus event + _overlayRevealed = false; + return; + } + showOpenVrKeyboard(focusText); + } + }); +} + + +void disableOpenVrKeyboard() { + if (disableSteamVrKeyboard) { + return; + } + QObject::disconnect(_overlayMenuConnection); + QObject::disconnect(_focusTextConnection); + QObject::disconnect(_focusConnection); +} + +bool isOpenVrKeyboardShown() { + return _keyboardShown; +} + + +void handleOpenVrEvents() { + if (!activeHmd) { + return; + } + Lock lock(mutex); + if (!activeHmd) { + return; + } + + vr::VREvent_t event; + while (activeHmd->PollNextEvent(&event, sizeof(event))) { + switch (event.eventType) { + case vr::VREvent_Quit: + activeHmd->AcknowledgeQuit_Exiting(); + QMetaObject::invokeMethod(qApp, "quit"); + break; + + case vr::VREvent_KeyboardDone: + finishOpenVrKeyboardInput(); + + // FALL THROUGH + case vr::VREvent_KeyboardClosed: + _keyboardFocusObject = nullptr; + _keyboardShown = false; + DependencyManager::get()->unfocusWindows(); + break; + + default: + break; + } + #if DEV_BUILD + qDebug() << "OpenVR: Event " << activeHmd->GetEventTypeNameFromEnum((vr::EVREventType)event.eventType) << "(" << event.eventType << ")"; + #endif + } + +} + +controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity) { + // When the sensor-to-world rotation is identity the coordinate axes look like this: + // + // user + // forward + // -z + // | + // y| user + // y o----x right + // o-----x user + // | up + // | + // z + // + // Rift + + // From ABOVE the hand canonical axes looks like this: + // + // | | | | y | | | | + // | | | | | | | | | + // | | | | | + // |left | / x---- + \ |right| + // | _/ z \_ | + // | | | | + // | | | | + // + + // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down + // the rotation to align the Touch axes with those of the hands is: + // + // touchToHand = halfTurnAboutY * quaterTurnAboutX + + // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. + // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that + // the combination (measurement * offset) is identity at this orientation. + // + // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) + // + // An approximate offset for the Touch can be obtained by inspection: + // + // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) + // + // So the full equation is: + // + // Q = combinedMeasurement * touchToHand + // + // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) + // + // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) + 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 touchToHand = yFlip * quarterX; + + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuarterZ = 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(leftQuarterZ * eighthX) * touchToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand; + + 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 glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; + static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; + + auto translationOffset = (isLeftHand ? leftTranslationOffset : rightTranslationOffset); + auto rotationOffset = (isLeftHand ? leftRotationOffset : rightRotationOffset); + + glm::vec3 position = extractTranslation(mat); + glm::quat rotation = glm::normalize(glm::quat_cast(mat)); + + position += rotation * translationOffset; + rotation = rotation * rotationOffset; + + // transform into avatar frame + auto result = controller::Pose(position, rotation); + // handle change in velocity due to translationOffset + result.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat)); + result.angularVelocity = angularVelocity; + return result; +} diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 81896a2ce5..19c9cbfff5 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -12,9 +12,19 @@ #include #include -bool isOculusPresent(); +#include +#include + +bool openVrSupported(); + vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); +void handleOpenVrEvents(); +bool openVrQuitRequested(); +void enableOpenVrKeyboard(PluginContainer* container); +void disableOpenVrKeyboard(); +bool isOpenVrKeyboardShown(); + template void openvr_for_each_eye(F f) { @@ -38,3 +48,15 @@ inline mat4 toGlm(const vr::HmdMatrix34_t& m) { m.m[0][3], m.m[1][3], m.m[2][3], 1.0f); return result; } + +inline vr::HmdMatrix34_t toOpenVr(const mat4& m) { + vr::HmdMatrix34_t result; + for (uint8_t i = 0; i < 3; ++i) { + for (uint8_t j = 0; j < 4; ++j) { + result.m[i][j] = m[j][i]; + } + } + return result; +} + +controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index fab383a955..85feebda11 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -11,8 +11,6 @@ #include "ViveControllerManager.h" -#include - #include #include #include @@ -20,8 +18,10 @@ #include #include #include -#include +#include #include +#include + #include @@ -38,10 +38,6 @@ vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); -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 char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; static const QString MENU_PARENT = "Avatar"; @@ -50,11 +46,9 @@ static const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; static const QString RENDER_CONTROLLERS = "Render Hand Controllers"; const QString ViveControllerManager::NAME = "OpenVR"; -static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); -static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); bool ViveControllerManager::isSupported() const { - return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent(); + return openVrSupported(); } bool ViveControllerManager::activate() { @@ -70,6 +64,8 @@ bool ViveControllerManager::activate() { } Q_ASSERT(_system); + enableOpenVrKeyboard(_container); + // OpenVR provides 3d mesh representations of the controllers // Disabled controller rendering code /* @@ -127,13 +123,14 @@ bool ViveControllerManager::activate() { auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(_inputDevice); _registeredWithInputMapper = true; - return true; } void ViveControllerManager::deactivate() { InputPlugin::deactivate(); + disableOpenVrKeyboard(); + _container->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS); _container->removeMenu(MENU_PATH); @@ -214,12 +211,13 @@ void ViveControllerManager::renderHand(const controller::Pose& pose, gpu::Batch& } -void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { auto userInputMapper = DependencyManager::get(); + handleOpenVrEvents(); // because update mutates the internal state we need to lock userInputMapper->withLock([&, this]() { - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + _inputDevice->update(deltaTime, inputCalibrationData); }); if (_inputDevice->_trackedControllers == 0 && _registeredWithInputMapper) { @@ -231,22 +229,36 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu if (!_registeredWithInputMapper && _inputDevice->_trackedControllers > 0) { userInputMapper->registerDevice(_inputDevice); _registeredWithInputMapper = true; - UserActivityLogger::getInstance().connectedDevice("spatial_controller", "steamVR"); } } -void ViveControllerManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void ViveControllerManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _poseStateMap.clear(); _buttonPressedMap.clear(); + // While the keyboard is open, we defer strictly to the keyboard values + if (isOpenVrKeyboardShown()) { + _axisStateMap.clear(); + return; + } + PerformanceTimer perfTimer("ViveControllerManager::update"); auto leftHandDeviceIndex = _system->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand); auto rightHandDeviceIndex = _system->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand); - if (!jointsCaptured) { - handleHandController(deltaTime, leftHandDeviceIndex, inputCalibrationData, true); - handleHandController(deltaTime, rightHandDeviceIndex, inputCalibrationData, false); + handleHandController(deltaTime, leftHandDeviceIndex, inputCalibrationData, true); + handleHandController(deltaTime, rightHandDeviceIndex, inputCalibrationData, false); + + // handle haptics + { + Locker locker(_lock); + if (_leftHapticDuration > 0.0f) { + hapticsHelper(deltaTime, true); + } + if (_rightHapticDuration > 0.0f) { + hapticsHelper(deltaTime, false); + } } int numTrackedControllers = 0; @@ -273,7 +285,6 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u vr::VRControllerState_t controllerState = vr::VRControllerState_t(); if (_system->GetControllerState(deviceIndex, &controllerState)) { - // process each button for (uint32_t i = 0; i < vr::k_EButton_Max; ++i) { auto mask = vr::ButtonMaskFromId((vr::EVRButtonId)i); @@ -286,7 +297,26 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u for (uint32_t i = 0; i < vr::k_unControllerStateAxisCount; i++) { handleAxisEvent(deltaTime, i, controllerState.rAxis[i].x, controllerState.rAxis[i].y, isLeftHand); } - } + + // pseudo buttons the depend on both of the above for-loops + partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_X, controller::LS_Y); + partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_X, controller::RS_Y); + } + } +} + +void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) { + // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. + const float CENTER_DEADBAND = 0.6f; + const float DIAGONAL_DIVIDE_IN_RADIANS = PI / 4.0f; + if (_buttonPressedMap.find(sButton) != _buttonPressedMap.end()) { + float absX = abs(_axisStateMap[xAxis]); + float absY = abs(_axisStateMap[yAxis]); + glm::vec2 cartesianQuadrantI(absX, absY); + float angle = glm::atan(cartesianQuadrantI.y / cartesianQuadrantI.x); + float radius = glm::length(cartesianQuadrantI); + bool isCenter = radius < CENTER_DEADBAND; + _buttonPressedMap.insert(isCenter ? centerPseudoButton : ((angle < DIAGONAL_DIVIDE_IN_RADIANS) ? xPseudoButton :yPseudoButton)); } } @@ -312,6 +342,11 @@ void ViveControllerManager::InputDevice::handleAxisEvent(float deltaTime, uint32 _axisStateMap[isLeftHand ? LY : RY] = stick.y; } else if (axis == vr::k_EButton_SteamVR_Trigger) { _axisStateMap[isLeftHand ? LT : RT] = x; + // The click feeling on the Vive controller trigger represents a value of *precisely* 1.0, + // so we can expose that as an additional button + if (x >= 1.0f) { + _buttonPressedMap.insert(isLeftHand ? LT_CLICK : RT_CLICK); + } } } @@ -331,12 +366,16 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint if (button == vr::k_EButton_ApplicationMenu) { _buttonPressedMap.insert(isLeftHand ? LEFT_APP_MENU : RIGHT_APP_MENU); } else if (button == vr::k_EButton_Grip) { - _buttonPressedMap.insert(isLeftHand ? LEFT_GRIP : RIGHT_GRIP); + _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP] = 1.0f; } else if (button == vr::k_EButton_SteamVR_Trigger) { _buttonPressedMap.insert(isLeftHand ? LT : RT); } else if (button == vr::k_EButton_SteamVR_Touchpad) { _buttonPressedMap.insert(isLeftHand ? LS : RS); } + } else { + if (button == vr::k_EButton_Grip) { + _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP] = 0.0f; + } } if (touched) { @@ -349,86 +388,60 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint void ViveControllerManager::InputDevice::handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand) { - // When the sensor-to-world rotation is identity the coordinate axes look like this: - // - // user - // forward - // -z - // | - // y| user - // y o----x right - // o-----x user - // | up - // | - // z - // - // Vive - // - - // From ABOVE the hand canonical axes looks like this: - // - // | | | | y | | | | - // | | | | | | | | | - // | | | | | - // |left | / x---- + \ |right| - // | _/ z \_ | - // | | | | - // | | | | - // - - // So when the user is standing in Vive space facing the -zAxis with hands outstretched and palms down - // the rotation to align the Vive axes with those of the hands is: - // - // QviveToHand = halfTurnAboutY * quaterTurnAboutX - - // Due to how the Vive controllers fit into the palm there is an offset that is different for each hand. - // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that - // the combination (measurement * offset) is identity at this orientation. - // - // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) - // - // An approximate offset for the Vive can be obtained by inspection: - // - // Qoffset = glm::inverse(glm::angleAxis(sign * PI/4.0f, zAxis) * glm::angleAxis(PI/2.0f, xAxis)) - // - // So the full equation is: - // - // Q = combinedMeasurement * viveToHand - // - // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) - // - // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) - - 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; - - auto translationOffset = (isLeftHand ? leftTranslationOffset : rightTranslationOffset); - auto rotationOffset = (isLeftHand ? leftRotationOffset : rightRotationOffset); - - glm::vec3 position = extractTranslation(mat); - glm::quat rotation = glm::normalize(glm::quat_cast(mat)); - - position += rotation * translationOffset; - rotation = rotation * rotationOffset; + auto pose = openVrControllerPoseToHandPose(isLeftHand, mat, linearVelocity, angularVelocity); // transform into avatar frame glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; - auto avatarPose = controller::Pose(position, rotation); - // handle change in velocity due to translationOffset - avatarPose.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat)); - avatarPose.angularVelocity = angularVelocity; - _poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] = avatarPose.transform(controllerToAvatar); + _poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] = pose.transform(controllerToAvatar); +} + +bool ViveControllerManager::InputDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) { + Locker locker(_lock); + if (hand == controller::BOTH || hand == controller::LEFT) { + if (strength == 0.0f) { + _leftHapticStrength = 0.0f; + _leftHapticDuration = 0.0f; + } else { + _leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength; + _leftHapticDuration = std::max(duration, _leftHapticDuration); + } + } + if (hand == controller::BOTH || hand == controller::RIGHT) { + if (strength == 0.0f) { + _rightHapticStrength = 0.0f; + _rightHapticDuration = 0.0f; + } else { + _rightHapticStrength = (duration > _rightHapticDuration) ? strength : _rightHapticStrength; + _rightHapticDuration = std::max(duration, _rightHapticDuration); + } + } + return true; +} + +void ViveControllerManager::InputDevice::hapticsHelper(float deltaTime, bool leftHand) { + auto handRole = leftHand ? vr::TrackedControllerRole_LeftHand : vr::TrackedControllerRole_RightHand; + auto deviceIndex = _system->GetTrackedDeviceIndexForControllerRole(handRole); + + if (_system->IsTrackedDeviceConnected(deviceIndex) && + _system->GetTrackedDeviceClass(deviceIndex) == vr::TrackedDeviceClass_Controller && + _trackedDevicePose[deviceIndex].bPoseIsValid) { + float strength = leftHand ? _leftHapticStrength : _rightHapticStrength; + float duration = leftHand ? _leftHapticDuration : _rightHapticDuration; + + // Vive Controllers only support duration up to 4 ms, which is short enough that any variation feels more like strength + const float MAX_HAPTIC_TIME = 3999.0f; // in microseconds + float hapticTime = strength * MAX_HAPTIC_TIME; + if (hapticTime < duration * 1000.0f) { + _system->TriggerHapticPulse(deviceIndex, 0, hapticTime); + } + + float remainingHapticTime = duration - (hapticTime / 1000.0f + deltaTime * 1000.0f); // in milliseconds + if (leftHand) { + _leftHapticDuration = remainingHapticTime; + } else { + _rightHapticDuration = remainingHapticTime; + } + } } controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableInputs() const { @@ -447,11 +460,23 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI // touch pad press makePair(LS, "LS"), makePair(RS, "RS"), + // Differentiate where we are in the touch pad click + makePair(LS_CENTER, "LSCenter"), + makePair(LS_X, "LSX"), + makePair(LS_Y, "LSY"), + makePair(RS_CENTER, "RSCenter"), + makePair(RS_X, "RSX"), + makePair(RS_Y, "RSY"), + // triggers makePair(LT, "LT"), makePair(RT, "RT"), + // Trigger clicks + makePair(LT_CLICK, "LTClick"), + makePair(RT_CLICK, "RTClick"), + // low profile side grip button. makePair(LEFT_GRIP, "LeftGrip"), makePair(RIGHT_GRIP, "RightGrip"), diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index d55d4e726c..95ff2f881a 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -31,17 +31,15 @@ namespace vr { class ViveControllerManager : public InputPlugin { Q_OBJECT public: - // Plugin functions - virtual bool isSupported() const override; - virtual bool isJointController() const override { return true; } + bool isSupported() const override; const QString& getName() const override { return NAME; } - virtual bool activate() override; - virtual void deactivate() override; + bool activate() override; + void deactivate() override; - virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void updateRendering(RenderArgs* args, render::ScenePointer scene, render::PendingChanges pendingChanges); @@ -53,16 +51,20 @@ private: InputDevice(vr::IVRSystem*& system) : controller::InputDevice("Vive"), _system(system) {} private: // Device functions - virtual controller::Input::NamedVector getAvailableInputs() const override; - virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; - virtual void focusOutEvent() override; + controller::Input::NamedVector getAvailableInputs() const override; + QString getDefaultMappingConfig() const override; + void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + void focusOutEvent() override; + + bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; + void hapticsHelper(float deltaTime, bool leftHand); void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand); void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand); void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand); + void partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int xPseudoButton, int yPseudoButton); class FilteredStick { public: @@ -91,8 +93,19 @@ private: FilteredStick _filteredLeftStick; FilteredStick _filteredRightStick; + // perform an action when the InputDevice mutex is acquired. + using Locker = std::unique_lock; + template + void withLock(F&& f) { Locker locker(_lock); f(); } + int _trackedControllers { 0 }; vr::IVRSystem*& _system; + float _leftHapticStrength { 0.0f }; + float _leftHapticDuration { 0.0f }; + float _rightHapticStrength { 0.0f }; + float _rightHapticDuration { 0.0f }; + mutable std::recursive_mutex _lock; + friend class ViveControllerManager; }; diff --git a/plugins/openvr/src/plugin.json b/plugins/openvr/src/plugin.json index 0967ef424b..d68c8e68d3 100644 --- a/plugins/openvr/src/plugin.json +++ b/plugins/openvr/src/plugin.json @@ -1 +1 @@ -{} +{"name":"OpenVR (Vive)"} diff --git a/plugins/pcmCodec/CMakeLists.txt b/plugins/pcmCodec/CMakeLists.txt new file mode 100644 index 0000000000..900a642a88 --- /dev/null +++ b/plugins/pcmCodec/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Created by Brad Hefta-Gaub on 6/9/2016 +# Copyright 2016 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 +# + +set(TARGET_NAME pcmCodec) +setup_hifi_client_server_plugin() +link_hifi_libraries(shared plugins) +install_beside_console() + diff --git a/plugins/pcmCodec/src/PCMCodecManager.cpp b/plugins/pcmCodec/src/PCMCodecManager.cpp new file mode 100644 index 0000000000..315d0622ab --- /dev/null +++ b/plugins/pcmCodec/src/PCMCodecManager.cpp @@ -0,0 +1,94 @@ +// +// PCMCodec.cpp +// plugins/pcmCodec/src +// +// Created by Brad Hefta-Gaub on 6/9/2016 +// Copyright 2016 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 "PCMCodecManager.h" + +const QString PCMCodec::NAME = "pcm"; + +void PCMCodec::init() { +} + +void PCMCodec::deinit() { +} + +bool PCMCodec::activate() { + CodecPlugin::activate(); + return true; +} + +void PCMCodec::deactivate() { + CodecPlugin::deactivate(); +} + + +bool PCMCodec::isSupported() const { + return true; +} + + + +Encoder* PCMCodec::createEncoder(int sampleRate, int numChannels) { + return this; +} + +Decoder* PCMCodec::createDecoder(int sampleRate, int numChannels) { + return this; +} + +void PCMCodec::releaseEncoder(Encoder* encoder) { + // do nothing +} + +void PCMCodec::releaseDecoder(Decoder* decoder) { + // do nothing +} + +const QString zLibCodec::NAME = "zlib"; + +void zLibCodec::init() { +} + +void zLibCodec::deinit() { +} + +bool zLibCodec::activate() { + CodecPlugin::activate(); + return true; +} + +void zLibCodec::deactivate() { + CodecPlugin::deactivate(); +} + +bool zLibCodec::isSupported() const { + return true; +} + +Encoder* zLibCodec::createEncoder(int sampleRate, int numChannels) { + return this; +} + +Decoder* zLibCodec::createDecoder(int sampleRate, int numChannels) { + return this; +} + +void zLibCodec::releaseEncoder(Encoder* encoder) { + // do nothing... it wasn't allocated +} + +void zLibCodec::releaseDecoder(Decoder* decoder) { + // do nothing... it wasn't allocated +} + diff --git a/plugins/pcmCodec/src/PCMCodecManager.h b/plugins/pcmCodec/src/PCMCodecManager.h new file mode 100644 index 0000000000..55d7c866f1 --- /dev/null +++ b/plugins/pcmCodec/src/PCMCodecManager.h @@ -0,0 +1,86 @@ +// +// PCMCodecManager.h +// plugins/pcmCodec/src +// +// Created by Brad Hefta-Gaub on 6/9/2016 +// Copyright 2016 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__PCMCodecManager_h +#define hifi__PCMCodecManager_h + +#include + +class PCMCodec : public CodecPlugin, public Encoder, public Decoder { + Q_OBJECT + +public: + // Plugin functions + bool isSupported() const override; + const QString& getName() const override { return NAME; } + + void init() override; + void deinit() override; + + /// Called when a plugin is being activated for use. May be called multiple times. + bool activate() override; + /// Called when a plugin is no longer being used. May be called multiple times. + void deactivate() override; + + virtual Encoder* createEncoder(int sampleRate, int numChannels) override; + virtual Decoder* createDecoder(int sampleRate, int numChannels) override; + virtual void releaseEncoder(Encoder* encoder) override; + virtual void releaseDecoder(Decoder* decoder) override; + + virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) override { + encodedBuffer = decodedBuffer; + } + virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) override { + decodedBuffer = encodedBuffer; + } + + virtual void trackLostFrames(int numFrames) override { } + +private: + static const QString NAME; +}; + +class zLibCodec : public CodecPlugin, public Encoder, public Decoder { + Q_OBJECT + +public: + // Plugin functions + bool isSupported() const override; + const QString& getName() const override { return NAME; } + + void init() override; + void deinit() override; + + /// Called when a plugin is being activated for use. May be called multiple times. + bool activate() override; + /// Called when a plugin is no longer being used. May be called multiple times. + void deactivate() override; + + virtual Encoder* createEncoder(int sampleRate, int numChannels) override; + virtual Decoder* createDecoder(int sampleRate, int numChannels) override; + virtual void releaseEncoder(Encoder* encoder) override; + virtual void releaseDecoder(Decoder* decoder) override; + + virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) override { + encodedBuffer = qCompress(decodedBuffer); + } + + virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) override { + decodedBuffer = qUncompress(encodedBuffer); + } + + virtual void trackLostFrames(int numFrames) override { } + +private: + static const QString NAME; +}; + +#endif // hifi__PCMCodecManager_h diff --git a/plugins/pcmCodec/src/PCMCodecProvider.cpp b/plugins/pcmCodec/src/PCMCodecProvider.cpp new file mode 100644 index 0000000000..351b1adf3f --- /dev/null +++ b/plugins/pcmCodec/src/PCMCodecProvider.cpp @@ -0,0 +1,51 @@ +// +// Created by Brad Hefta-Gaub on 6/9/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include +#include + +#include +#include + +#include "PCMCodecManager.h" + +class PCMCodecProvider : public QObject, public CodecProvider { + Q_OBJECT + Q_PLUGIN_METADATA(IID CodecProvider_iid FILE "plugin.json") + Q_INTERFACES(CodecProvider) + +public: + PCMCodecProvider(QObject* parent = nullptr) : QObject(parent) {} + virtual ~PCMCodecProvider() {} + + virtual CodecPluginList getCodecPlugins() override { + static std::once_flag once; + std::call_once(once, [&] { + + CodecPluginPointer pcmCodec(new PCMCodec()); + if (pcmCodec->isSupported()) { + _codecPlugins.push_back(pcmCodec); + } + + CodecPluginPointer zlibCodec(new zLibCodec()); + if (zlibCodec->isSupported()) { + _codecPlugins.push_back(zlibCodec); + } + + }); + return _codecPlugins; + } + +private: + CodecPluginList _codecPlugins; +}; + +#include "PCMCodecProvider.moc" diff --git a/plugins/pcmCodec/src/plugin.json b/plugins/pcmCodec/src/plugin.json new file mode 100644 index 0000000000..2d86251845 --- /dev/null +++ b/plugins/pcmCodec/src/plugin.json @@ -0,0 +1 @@ +{"name":"PCM Codec"} diff --git a/script-archive/FlockOfbirds.js b/script-archive/FlockOfbirds.js index f466fa2909..c2fb54f0a6 100644 --- a/script-archive/FlockOfbirds.js +++ b/script-archive/FlockOfbirds.js @@ -3,8 +3,8 @@ // examples // // Copyright 2014 High Fidelity, Inc. -// Creates a flock of birds that fly around and chirp, staying inside the corners of the box defined -// at the start of the script. +// Creates a flock of birds that fly around and chirp, staying inside the corners of the box defined +// at the start of the script. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -13,12 +13,12 @@ // The rectangular area in the domain where the flock will fly var lowerCorner = { x: 0, y: 0, z: 0 }; var upperCorner = { x: 30, y: 10, z: 30 }; -var STARTING_FRACTION = 0.25; +var STARTING_FRACTION = 0.25; var NUM_BIRDS = 50; var UPDATE_INTERVAL = 0.016; -var playSounds = true; -var SOUND_PROBABILITY = 0.001; +var playSounds = true; +var SOUND_PROBABILITY = 0.001; var STARTING_LIFETIME = (1.0 / SOUND_PROBABILITY) * UPDATE_INTERVAL * 10; var numPlaying = 0; var BIRD_SIZE = 0.08; @@ -36,17 +36,17 @@ var ALIGNMENT_FORCE = 1.5; var COHESION_FORCE = 1.0; var MAX_COHESION_VELOCITY = 0.5; -var followBirds = false; +var followBirds = false; var AVATAR_FOLLOW_RATE = 0.001; var AVATAR_FOLLOW_VELOCITY_TIMESCALE = 2.0; var AVATAR_FOLLOW_ORIENTATION_RATE = 0.005; -var floor = false; +var floor = false; var MAKE_FLOOR = false; var averageVelocity = { x: 0, y: 0, z: 0 }; var averagePosition = { x: 0, y: 0, z: 0 }; -var birdsLoaded = false; +var birdsLoaded = false; var oldAvatarOrientation; var oldAvatarPosition; @@ -79,10 +79,10 @@ function updateBirds(deltaTime) { birds[i].entityId = false; return; } - // Sum up average position and velocity + // Sum up average position and velocity if (Vec3.length(properties.velocity) > MIN_ALIGNMENT_VELOCITY) { sumVelocity = Vec3.sum(sumVelocity, properties.velocity); - birdVelocitiesCounted += 1; + birdVelocitiesCounted += 1; } sumPosition = Vec3.sum(sumPosition, properties.position); birdPositionsCounted += 1; @@ -93,10 +93,10 @@ function updateBirds(deltaTime) { var randomVelocity = randomVector(RANDOM_FLAP_VELOCITY); randomVelocity.y = FLAP_UP + Math.random() * FLAP_UP; - // Alignment Velocity - var alignmentVelocityMagnitude = Math.min(MAX_ALIGNMENT_VELOCITY, Vec3.length(Vec3.multiply(ALIGNMENT_FORCE, averageVelocity))); + // Alignment Velocity + var alignmentVelocityMagnitude = Math.min(MAX_ALIGNMENT_VELOCITY, Vec3.length(Vec3.multiply(ALIGNMENT_FORCE, averageVelocity))); var alignmentVelocity = Vec3.multiply(alignmentVelocityMagnitude, Vec3.normalize(averageVelocity)); - alignmentVelocity.y *= VERTICAL_ALIGNMENT_COUPLING; + alignmentVelocity.y *= VERTICAL_ALIGNMENT_COUPLING; // Cohesion var distanceFromCenter = Vec3.length(Vec3.subtract(averagePosition, properties.position)); @@ -107,10 +107,10 @@ function updateBirds(deltaTime) { Entities.editEntity(birds[i].entityId, { velocity: Vec3.sum(properties.velocity, newVelocity) }); - } + } // Check whether to play a chirp - if (playSounds && (!birds[i].audioId || !birds[i].audioId.isPlaying) && (Math.random() < ((numPlaying > 0) ? SOUND_PROBABILITY / numPlaying : SOUND_PROBABILITY))) { + if (playSounds && (!birds[i].audioId || !birds[i].audioId.playing) && (Math.random() < ((numPlaying > 0) ? SOUND_PROBABILITY / numPlaying : SOUND_PROBABILITY))) { var options = { position: properties.position, volume: BIRD_MASTER_VOLUME @@ -126,43 +126,43 @@ function updateBirds(deltaTime) { // Change size, and update lifetime to keep bird alive Entities.editEntity(birds[i].entityId, { dimensions: Vec3.multiply(1.5, properties.dimensions), lifetime: properties.ageInSeconds + STARTING_LIFETIME}); - + } else if (birds[i].audioId) { - // If bird is playing a chirp - if (!birds[i].audioId.isPlaying) { + // If bird is playing a chirp + if (!birds[i].audioId.playing) { Entities.editEntity(birds[i].entityId, { dimensions: { x: BIRD_SIZE, y: BIRD_SIZE, z: BIRD_SIZE }}); numPlaying--; - } + } } // Keep birds in their 'cage' var bounce = false; - var newVelocity = properties.velocity; - var newPosition = properties.position; + var newVelocity = properties.velocity; + var newPosition = properties.position; if (properties.position.x < lowerCorner.x) { - newPosition.x = lowerCorner.x; + newPosition.x = lowerCorner.x; newVelocity.x *= -1.0; bounce = true; } else if (properties.position.x > upperCorner.x) { - newPosition.x = upperCorner.x; + newPosition.x = upperCorner.x; newVelocity.x *= -1.0; bounce = true; } if (properties.position.y < lowerCorner.y) { - newPosition.y = lowerCorner.y; + newPosition.y = lowerCorner.y; newVelocity.y *= -1.0; bounce = true; } else if (properties.position.y > upperCorner.y) { - newPosition.y = upperCorner.y; + newPosition.y = upperCorner.y; newVelocity.y *= -1.0; bounce = true; - } + } if (properties.position.z < lowerCorner.z) { - newPosition.z = lowerCorner.z; + newPosition.z = lowerCorner.z; newVelocity.z *= -1.0; bounce = true; } else if (properties.position.z > upperCorner.z) { - newPosition.z = upperCorner.z; + newPosition.z = upperCorner.z; newVelocity.z *= -1.0; bounce = true; } @@ -171,7 +171,7 @@ function updateBirds(deltaTime) { } } } - // Update average velocity and position of flock + // Update average velocity and position of flock if (birdVelocitiesCounted > 0) { averageVelocity = Vec3.multiply(1.0 / birdVelocitiesCounted, sumVelocity); //print(Vec3.length(averageVelocity)); @@ -184,10 +184,10 @@ function updateBirds(deltaTime) { MyAvatar.orientation = Quat.mix(MyAvatar.orientation, birdDirection, AVATAR_FOLLOW_ORIENTATION_RATE); } } - } + } if (birdPositionsCounted > 0) { averagePosition = Vec3.multiply(1.0 / birdPositionsCounted, sumPosition); - // If Following birds, update position + // If Following birds, update position if (followBirds) { MyAvatar.position = Vec3.sum(Vec3.multiply(AVATAR_FOLLOW_RATE, MyAvatar.position), Vec3.multiply(1.0 - AVATAR_FOLLOW_RATE, averagePosition)); } @@ -211,12 +211,12 @@ Script.scriptEnding.connect(function() { }); function loadBirds(howMany) { - oldAvatarOrientation = MyAvatar.orientation; + oldAvatarOrientation = MyAvatar.orientation; oldAvatarPosition = MyAvatar.position; var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw"]; /* Here are more sounds/species you can use - , "mexicanWhipoorwill.raw", + , "mexicanWhipoorwill.raw", "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav", "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav", "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav", @@ -252,19 +252,19 @@ function loadBirds(howMany) { { red: 216, green: 153, blue: 99 }, { red: 242, green: 226, blue: 64 } ]; - + var SOUND_BASE_URL = "http://public.highfidelity.io/sounds/Animals/"; - + for (var i = 0; i < howMany; i++) { var whichBird = Math.floor(Math.random() * sound_filenames.length); - var position = { - x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.x - lowerCorner.x) * STARTING_FRACTION, - y: lowerCorner.y + (upperCorner.y - lowerCorner.y) / 2.0 + (Math.random() - 0.5) * (upperCorner.y - lowerCorner.y) * STARTING_FRACTION, + var position = { + x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.x - lowerCorner.x) * STARTING_FRACTION, + y: lowerCorner.y + (upperCorner.y - lowerCorner.y) / 2.0 + (Math.random() - 0.5) * (upperCorner.y - lowerCorner.y) * STARTING_FRACTION, z: lowerCorner.z + (upperCorner.z - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.z - lowerCorner.z) * STARTING_FRACTION - }; + }; birds.push({ - sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[whichBird]), + sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[whichBird]), entityId: Entities.addEntity({ type: "Sphere", position: position, @@ -282,8 +282,8 @@ function loadBirds(howMany) { } if (MAKE_FLOOR) { var FLOOR_THICKNESS = 0.05; - floor = Entities.addEntity({ type: "Box", position: { x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0, - y: lowerCorner.y, + floor = Entities.addEntity({ type: "Box", position: { x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0, + y: lowerCorner.y, z: lowerCorner.z + (upperCorner.z - lowerCorner.z) / 2.0 }, dimensions: { x: (upperCorner.x - lowerCorner.x), y: FLOOR_THICKNESS, z: (upperCorner.z - lowerCorner.z)}, color: {red: 100, green: 100, blue: 100} diff --git a/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js b/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js index de7baba267..30567b4fc7 100644 --- a/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js +++ b/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js @@ -40,6 +40,7 @@ var DEFAULT_SOUND_DATA = { Script.include("../../libraries/utils.js"); Agent.isAvatar = true; // This puts a robot at 0,0,0, but is currently necessary in order to use AvatarList. +Avatar.skeletonModelURL = "http://invalid-url"; function ignore() {} function debug() { // Display the arguments not just [Object object]. //print.apply(null, [].map.call(arguments, JSON.stringify)); @@ -48,7 +49,7 @@ function debug() { // Display the arguments not just [Object object]. EntityViewer.setCenterRadius(QUERY_RADIUS); // ENTITY DATA CACHE -// +// var entityCache = {}; // A dictionary of unexpired EntityData objects. var examinationCount = 0; function EntityDatum(entityIdentifier) { // Just the data of an entity that we need to know about. @@ -145,7 +146,7 @@ function EntityDatum(entityIdentifier) { // Just the data of an entity that we n return; } that.injector.setOptions(options); // PLAYING => UPDATE POSITION ETC - if (!that.injector.isPlaying) { // Subtle: a looping sound will not check playbackGap. + if (!that.injector.playing) { // Subtle: a looping sound will not check playbackGap. if (repeat()) { // WAITING => PLAYING // Setup next play just once, now. Changes won't be looked at while we wait. that.playAfter = randomizedNextPlay(); @@ -207,7 +208,7 @@ function updateAllEntityData() { // A fast update of all entities we know about. stats.entities++; if (datum.url) { stats.sounds++; - if (datum.injector && datum.injector.isPlaying) { + if (datum.injector && datum.injector.playing) { stats.playing++; } } diff --git a/script-archive/avatarSelector.js b/script-archive/avatarSelector.js index dc2916a1a8..47740ef0b3 100644 --- a/script-archive/avatarSelector.js +++ b/script-archive/avatarSelector.js @@ -283,7 +283,7 @@ function actionStartEvent(event) { if (avatarIndex < avatars.length) { var actionPlace = avatars[avatarIndex]; - print("Changing avatar to " + actionPlace.name + print("Changing avatar to " + actionPlace.name + " after click on panel " + panelIndex + " with avatar index " + avatarIndex); MyAvatar.useFullAvatarURL(actionPlace.content_url); @@ -395,7 +395,7 @@ function update(deltaTime) { Overlays.editOverlay(descriptionText, { position: textOverlayPosition() }); // if the reticle is up then we may need to play the next muzak - if (currentMuzakInjector && !currentMuzakInjector.isPlaying) { + if (currentMuzakInjector && !currentMuzakInjector.playing) { playNextMuzak(); } } diff --git a/script-archive/baseball/baseballCrowd.js b/script-archive/baseball/baseballCrowd.js index de9b53f9ec..1459ce6e67 100644 --- a/script-archive/baseball/baseballCrowd.js +++ b/script-archive/baseball/baseballCrowd.js @@ -21,7 +21,7 @@ var CHATTER_VOLUME = 0.20 var EXTRA_VOLUME = 0.25 function playChatter() { - if (chatter.downloaded && !chatter.isPlaying) { + if (chatter.downloaded && !chatter.playing) { Audio.playSound(chatter, { loop: true, volume: CHATTER_VOLUME }); } } @@ -31,7 +31,7 @@ chatter.ready.connect(playChatter); var currentInjector = null; function playRandomExtras() { - if ((!currentInjector || !currentInjector.isPlaying) && (Math.random() < (1.0 / 1800.0))) { + if ((!currentInjector || !currentInjector.playing) && (Math.random() < (1.0 / 1800.0))) { // play a random extra sound about every 30s currentInjector = Audio.playSound( extras[Math.floor(Math.random() * extras.length)], diff --git a/script-archive/controllers/hydra/airGuitar.js b/script-archive/controllers/hydra/airGuitar.js index f8606808c1..73c7099eed 100644 --- a/script-archive/controllers/hydra/airGuitar.js +++ b/script-archive/controllers/hydra/airGuitar.js @@ -22,12 +22,12 @@ function printVector(v) { return; } -function vMinus(a, b) { +function vMinus(a, b) { var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; return rval; } -// The model file to be used for the guitar +// The model file to be used for the guitar var guitarModel = HIFI_PUBLIC_BUCKET + "models/attachments/guitar.fst"; // Load sounds that will be played @@ -47,7 +47,7 @@ chords[6] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Me chords[7] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Metal+E+short.raw"); chords[8] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Metal+G+short.raw"); -// Steel Guitar +// Steel Guitar chords[9] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Steel+A.raw"); chords[10] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Steel+B.raw"); chords[11] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Steel+E.raw"); @@ -83,8 +83,8 @@ if (leftHanded) { } var lastPosition = { x: 0.0, - y: 0.0, - z: 0.0 }; + y: 0.0, + z: 0.0 }; var audioInjector = null; var selectorPressed = false; @@ -106,7 +106,7 @@ function checkHands(deltaTime) { var chord = Controller.getValue(chordTrigger); if (volume > 1.0) volume = 1.0; - if ((chord > 0.1) && audioInjector && audioInjector.isPlaying) { + if ((chord > 0.1) && audioInjector && audioInjector.playing) { // If chord finger trigger pulled, stop current chord print("stopping chord because cord trigger pulled"); audioInjector.stop(); @@ -119,7 +119,7 @@ function checkHands(deltaTime) { guitarSelector += NUM_CHORDS; if (guitarSelector >= NUM_CHORDS * NUM_GUITARS) { guitarSelector = 0; - } + } print("new guitarBase: " + guitarSelector); stopAudio(true); selectorPressed = true; @@ -160,7 +160,7 @@ function checkHands(deltaTime) { } function stopAudio(killInjector) { - if (audioInjector && audioInjector.isPlaying) { + if (audioInjector && audioInjector.playing) { print("stopped sound"); audioInjector.stop(); } @@ -212,4 +212,3 @@ function scriptEnding() { Script.update.connect(checkHands); Script.scriptEnding.connect(scriptEnding); Controller.keyPressEvent.connect(keyPressEvent); - diff --git a/script-archive/drylake/ratCreator.js b/script-archive/drylake/ratCreator.js index 60ccf1a1a3..6f6b322f84 100644 --- a/script-archive/drylake/ratCreator.js +++ b/script-archive/drylake/ratCreator.js @@ -340,7 +340,7 @@ function moveRats() { var metaRat = getMetaRatByRat(rat); if (metaRat !== undefined) { if (metaRat.injector !== undefined) { - if (metaRat.injector.isPlaying === true) { + if (metaRat.injector.playing === true) { metaRat.injector.options = { loop: true, position: ratPosition diff --git a/script-archive/entityScripts/movable.js b/script-archive/entityScripts/movable.js index b7ecfbbc8e..06b30ce15e 100644 --- a/script-archive/entityScripts/movable.js +++ b/script-archive/entityScripts/movable.js @@ -8,7 +8,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -(function(){ +(function(){ this.entityID = null; this.properties = null; @@ -30,13 +30,13 @@ "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove2.wav", "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove3.wav" ]; - + this.turnSoundURLS = [ "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove1.wav", "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove2.wav", "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove3.wav" - + // TODO: determine if these or other turn sounds work better than move sounds. //"http://public.highfidelity.io/sounds/MovingFurniture/FurnitureTurn1.wav", //"http://public.highfidelity.io/sounds/MovingFurniture/FurnitureTurn2.wav", @@ -50,7 +50,7 @@ this.turnSound = null; this.moveInjector = null; this.turnInjector = null; - + var debug = false; var displayRotateTargets = true; // change to false if you don't want the rotate targets var rotateOverlayTargetSize = 10000; // really big target @@ -61,12 +61,12 @@ var yawZero; var rotationNormal; var yawNormal; - var stopSoundDelay = 100; // number of msecs of not moving to have sound stop - + var stopSoundDelay = 100; // number of msecs of not moving to have sound stop + this.getRandomInt = function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; - } - + } + this.downloadSounds = function() { for (var i = 0; i < this.moveSoundURLS.length; i++) { this.moveSounds[i] = SoundCache.getSound(this.moveSoundURLS[i]); @@ -95,7 +95,7 @@ if (debug) { print("playMoveSound() --- calling this.moveInjector = Audio.playSound(this.moveSound...)"); } - + if (!this.moveInjector) { this.moveInjector = Audio.playSound(this.moveSound, { position: this.properties.position, loop: true, volume: 0.1 }); } else { @@ -148,7 +148,7 @@ var upVector = { x: 0, y: 1, z: 0 }; var intersection = this.rayPlaneIntersection(pickRay.origin, pickRay.direction, this.properties.position, upVector); - + var newPosition = Vec3.sum(intersection, this.graboffset); Entities.editEntity(this.entityID, { position: newPosition }); }; @@ -158,7 +158,7 @@ var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) var upVector = { x: 0, y: 1, z: 0 }; var intersection = this.rayPlaneIntersection(pickRay.origin, pickRay.direction, - this.properties.position, upVector); + this.properties.position, upVector); this.graboffset = Vec3.subtract(this.properties.position, intersection); }; @@ -183,18 +183,18 @@ this.lastMovedPosition.y = mouseEvent.y; } } - + this.move = function(mouseEvent) { this.updatePosition(mouseEvent); - if (this.moveInjector === null || !this.moveInjector.isPlaying) { + if (this.moveInjector === null || !this.moveInjector.playing) { this.playMoveSound(); } }; - + this.release = function(mouseEvent) { this.updatePosition(mouseEvent); }; - + this.rotate = function(mouseEvent) { var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) var result = Overlays.findRayIntersection(pickRay); @@ -205,7 +205,7 @@ var centerToZero = Vec3.subtract(center, zero); var centerToIntersect = Vec3.subtract(center, result.intersection); var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - + var distanceFromCenter = Vec3.distance(center, result.intersection); var snapToInner = false; // var innerRadius = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; @@ -213,10 +213,10 @@ angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; snapToInner = true; } - + var yawChange = Quat.fromVec3Degrees({ x: 0, y: angleFromZero, z: 0 }); Entities.editEntity(this.entityID, { rotation: Quat.multiply(yawChange, this.originalRotation) }); - + // update the rotation display accordingly... var startAtCurrent = 360-angleFromZero; @@ -245,7 +245,7 @@ } } - if (this.turnInjector === null || !this.turnInjector.isPlaying) { + if (this.turnInjector === null || !this.turnInjector.playing) { this.playTurnSound(); } }; @@ -267,7 +267,7 @@ this.rotateOverlayOuter = null; this.rotateOverlayCurrent = null; } - + this.displayRotateOverlay = function(mouseEvent) { var yawOverlayAngles = { x: 90, y: 0, z: 0 }; var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); @@ -356,14 +356,14 @@ var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) var result = Overlays.findRayIntersection(pickRay); yawZero = result.intersection; - + }; - + this.preload = function(entityID) { this.updateProperties(entityID); // All callbacks start by updating the properties this.downloadSounds(); }; - + this.clickDownOnEntity = function(entityID, mouseEvent) { this.updateProperties(entityID); // All callbacks start by updating the properties this.grab(mouseEvent); @@ -372,13 +372,13 @@ var nowMSecs = nowDate.getTime(); this.clickedAt = nowMSecs; this.firstHolding = true; - + this.clicked.x = mouseEvent.x; this.clicked.y = mouseEvent.y; this.lastMovedPosition.x = mouseEvent.x; this.lastMovedPosition.y = mouseEvent.y; this.lastMovedMSecs = nowMSecs; - + this.pickRandomSounds(); }; @@ -391,7 +391,7 @@ if (this.clicked.x == mouseEvent.x && this.clicked.y == mouseEvent.y) { var d = new Date(); var now = d.getTime(); - + if (now - this.clickedAt > 500) { this.displayRotateOverlay(mouseEvent); this.firstHolding = false; @@ -402,13 +402,13 @@ this.firstHolding = false; } } - + if (this.rotateMode) { this.rotate(mouseEvent); } else { this.move(mouseEvent); } - + this.stopSoundIfNotMoving(mouseEvent); }; this.clickReleaseOnEntity = function(entityID, mouseEvent) { @@ -418,7 +418,7 @@ } else { this.release(mouseEvent); } - + if (this.rotateOverlayTarget != null) { this.cleanupRotateOverlay(); this.rotateMode = false; diff --git a/script-archive/example/audio/birdSongs.js b/script-archive/example/audio/birdSongs.js index 557fc81f5b..9e949a19ed 100644 --- a/script-archive/example/audio/birdSongs.js +++ b/script-archive/example/audio/birdSongs.js @@ -22,7 +22,7 @@ var BIRD_VELOCITY = 2.0; var LIGHT_RADIUS = 10.0; var BIRD_MASTER_VOLUME = 0.5; -var useLights = true; +var useLights = true; function randomVector(scale) { return { x: Math.random() * scale - scale / 2.0, y: Math.random() * scale - scale / 2.0, z: Math.random() * scale - scale / 2.0 }; @@ -33,11 +33,11 @@ function maybePlaySound(deltaTime) { // Set the location and other info for the sound to play var whichBird = Math.floor(Math.random() * birds.length); //print("playing sound # " + whichBird); - var position = { - x: lowerCorner.x + Math.random() * (upperCorner.x - lowerCorner.x), - y: lowerCorner.y + Math.random() * (upperCorner.y - lowerCorner.y), - z: lowerCorner.z + Math.random() * (upperCorner.z - lowerCorner.z) - }; + var position = { + x: lowerCorner.x + Math.random() * (upperCorner.x - lowerCorner.x), + y: lowerCorner.y + Math.random() * (upperCorner.y - lowerCorner.y), + z: lowerCorner.z + Math.random() * (upperCorner.z - lowerCorner.z) + }; var options = { position: position, volume: BIRD_MASTER_VOLUME @@ -63,31 +63,31 @@ function maybePlaySound(deltaTime) { constantAttenuation: 0, linearAttenuation: 4.0, - quadraticAttenuation: 2.0, + quadraticAttenuation: 2.0, lifetime: 10 }); } - + playing.push({ audioId: Audio.playSound(birds[whichBird].sound, options), entityId: entityId, lightId: lightId, color: birds[whichBird].color }); } if (playing.length != numPlaying) { numPlaying = playing.length; //print("number playing = " + numPlaying); - } + } for (var i = 0; i < playing.length; i++) { - if (!playing[i].audioId.isPlaying) { + if (!playing[i].audioId.playing) { Entities.deleteEntity(playing[i].entityId); if (useLights) { Entities.deleteEntity(playing[i].lightId); - } + } playing.splice(i, 1); } else { var loudness = playing[i].audioId.loudness; var newColor = { red: playing[i].color.red, green: playing[i].color.green, blue: playing[i].color.blue }; if (loudness > 0.05) { - newColor.red *= (1.0 - loudness); - newColor.green *= (1.0 - loudness); - newColor.blue *= (1.0 - loudness); + newColor.red *= (1.0 - loudness); + newColor.green *= (1.0 - loudness); + newColor.blue *= (1.0 - loudness); } var properties = Entities.getEntityProperties(playing[i].entityId); var newPosition = Vec3.sum(properties.position, randomVector(BIRD_VELOCITY * deltaTime)); @@ -120,7 +120,7 @@ Script.scriptEnding.connect(function() { }); function loadBirds() { - var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw", "mexicanWhipoorwill.raw", + var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw", "mexicanWhipoorwill.raw", "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav", "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav", "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav", @@ -155,13 +155,13 @@ function loadBirds() { { red: 216, green: 153, blue: 99 }, { red: 242, green: 226, blue: 64 } ]; - + var SOUND_BASE_URL = "http://public.highfidelity.io/sounds/Animals/"; - + for (var i = 0; i < sound_filenames.length; i++) { birds.push({ - sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[i]), - color: colors[i] + sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[i]), + color: colors[i] }); } -} \ No newline at end of file +} diff --git a/script-archive/lobby.js b/script-archive/lobby.js index 3095740c93..6fa4a42cb6 100644 --- a/script-archive/lobby.js +++ b/script-archive/lobby.js @@ -88,19 +88,19 @@ var DRONE_VOLUME = 0.3; function drawLobby() { if (!panelWall) { print("Adding overlays for the lobby panel wall and orb shell."); - + var cameraEuler = Quat.safeEulerAngles(Camera.orientation); var towardsMe = Quat.angleAxis(cameraEuler.y + 180, { x: 0, y: 1, z: 0}); - + var orbPosition = Vec3.sum(Camera.position, Vec3.multiplyQbyV(towardsMe, ORB_SHIFT)); - + var panelWallProps = { url: LOBBY_PANEL_WALL_URL, position: Vec3.sum(orbPosition, Vec3.multiplyQbyV(towardsMe, panelsCenterShift)), rotation: towardsMe, dimensions: panelsDimensions }; - + var orbShellProps = { url: LOBBY_SHELL_URL, position: orbPosition, @@ -128,13 +128,13 @@ function drawLobby() { visible: false, isFacingAvatar: true }; - + avatarStickPosition = MyAvatar.position; - panelWall = Overlays.addOverlay("model", panelWallProps); + panelWall = Overlays.addOverlay("model", panelWallProps); orbShell = Overlays.addOverlay("model", orbShellProps); descriptionText = Overlays.addOverlay("text3d", descriptionTextProps); - + if (droneSound.downloaded) { // start the drone sound if (!currentDrone) { @@ -143,7 +143,7 @@ function drawLobby() { currentDrone.restart(); } } - + // start one of our muzak sounds playRandomMuzak(); } @@ -157,31 +157,31 @@ function changeLobbyTextures() { req.send(); places = JSON.parse(req.responseText).data.places; - + var NUM_PANELS = places.length; - var textureProp = { + var textureProp = { textures: {} }; - + for (var j = 0; j < NUM_PANELS; j++) { var panelIndex = placeIndexToPanelIndex(j); textureProp["textures"]["file" + panelIndex] = places[j].previews.lobby; }; - + Overlays.editOverlay(panelWall, textureProp); } var MUZAK_VOLUME = 0.1; -function playCurrentSound(secondOffset) { +function playCurrentSound(secondOffset) { if (currentSound == latinSound) { if (!latinInjector) { latinInjector = Audio.playSound(latinSound, { localOnly: true, secondOffset: secondOffset, volume: MUZAK_VOLUME }); } else { latinInjector.restart(); } - + currentMuzakInjector = latinInjector; } else if (currentSound == elevatorSound) { if (!elevatorInjector) { @@ -189,7 +189,7 @@ function playCurrentSound(secondOffset) { } else { elevatorInjector.restart(); } - + currentMuzakInjector = elevatorInjector; } } @@ -205,14 +205,14 @@ function playNextMuzak() { currentSound = latinSound; } } - + playCurrentSound(0); } } function playRandomMuzak() { currentSound = null; - + if (latinSound.downloaded && elevatorSound.downloaded) { currentSound = Math.random() < 0.5 ? latinSound : elevatorSound; } else if (latinSound.downloaded) { @@ -220,11 +220,11 @@ function playRandomMuzak() { } else if (elevatorSound.downloaded) { currentSound = elevatorSound; } - + if (currentSound) { // pick a random number of seconds from 0-10 to offset the muzak var secondOffset = Math.random() * 10; - + playCurrentSound(secondOffset); } else { currentMuzakInjector = null; @@ -233,36 +233,36 @@ function playRandomMuzak() { function cleanupLobby() { toggleEnvironmentRendering(true); - + // for each of the 21 placeholder textures, set them back to default so the cached model doesn't have changed textures var panelTexturesReset = {}; panelTexturesReset["textures"] = {}; - - for (var j = 0; j < MAX_NUM_PANELS; j++) { + + for (var j = 0; j < MAX_NUM_PANELS; j++) { panelTexturesReset["textures"]["file" + (j + 1)] = LOBBY_BLANK_PANEL_TEXTURE_URL; }; - + Overlays.editOverlay(panelWall, panelTexturesReset); - + Overlays.deleteOverlay(panelWall); Overlays.deleteOverlay(orbShell); Overlays.deleteOverlay(descriptionText); - + panelWall = false; orbShell = false; - + if (currentDrone) { currentDrone.stop(); currentDrone = null } - + if (currentMuzakInjector) { currentMuzakInjector.stop(); currentMuzakInjector = null; } - + places = {}; - + } function actionStartEvent(event) { @@ -271,19 +271,19 @@ function actionStartEvent(event) { // check if we hit a panel and if we should jump there var result = Overlays.findRayIntersection(event.actionRay); if (result.intersects && result.overlayID == panelWall) { - + var panelName = result.extraInfo; - + var panelStringIndex = panelName.indexOf("Panel"); if (panelStringIndex != -1) { var panelIndex = parseInt(panelName.slice(5)); var placeIndex = panelIndexToPlaceIndex(panelIndex); if (placeIndex < places.length) { var actionPlace = places[placeIndex]; - - print("Jumping to " + actionPlace.name + " at " + actionPlace.address + + print("Jumping to " + actionPlace.name + " at " + actionPlace.address + " after click on panel " + panelIndex + " with place index " + placeIndex); - + Window.location = actionPlace.address; maybeCleanupLobby(); } @@ -328,7 +328,7 @@ function handleLookAt(pickRay) { var placeIndex = panelIndexToPlaceIndex(panelIndex); if (placeIndex < places.length) { var actionPlace = places[placeIndex]; - + if (actionPlace.description == "") { Overlays.editOverlay(descriptionText, { text: actionPlace.name, visible: showText }); } else { @@ -378,7 +378,7 @@ function update(deltaTime) { Overlays.editOverlay(descriptionText, { position: textOverlayPosition() }); // if the reticle is up then we may need to play the next muzak - if (currentMuzakInjector && !currentMuzakInjector.isPlaying) { + if (currentMuzakInjector && !currentMuzakInjector.playing) { playNextMuzak(); } } diff --git a/script-archive/playTestSound.js b/script-archive/playTestSound.js index 318df6a257..573c8879c4 100644 --- a/script-archive/playTestSound.js +++ b/script-archive/playTestSound.js @@ -2,12 +2,12 @@ // playTestSound.js // examples // -// Created by Philip Rosedale +// Created by Philip Rosedale // Copyright 2014 High Fidelity, Inc. // -// Creates an object in front of you that changes color and plays a light -// at the start of a drum clip that loops. As you move away it will tell you in the -// log how many meters you are from the source. +// Creates an object in front of you that changes color and plays a light +// at the start of a drum clip that loops. As you move away it will tell you in the +// log how many meters you are from the source. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -17,7 +17,7 @@ var sound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Dru var position = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), Quat.getFront(MyAvatar.orientation)); -var time; +var time; var soundPlaying = null; var baseColor = { red: 100, green: 100, blue: 100 }; @@ -38,8 +38,8 @@ var box = Entities.addEntity({ function checkSound(deltaTime) { var started = false; - if (!sound.downloaded) { - return; + if (!sound.downloaded) { + return; } if (soundPlaying == null) { soundPlaying = Audio.playSound(sound, { @@ -47,9 +47,9 @@ function checkSound(deltaTime) { volume: 1.0, loop: false } ); started = true; - } else if (!soundPlaying.isPlaying) { + } else if (!soundPlaying.playing) { soundPlaying.restart(); - started = true; + started = true; } if (started) { Entities.editEntity(box, { color: litColor }); @@ -67,19 +67,19 @@ function checkSound(deltaTime) { lifetime: lightTime / 1000 }); Script.setTimeout(resetColor, lightTime); - } + } var currentDistance = Vec3.distance(MyAvatar.position, position); if (Math.abs(currentDistance - distance) > 1.0) { print("Distance from source: " + currentDistance); distance = currentDistance; - } + } } function resetColor() { Entities.editEntity(box, { color: baseColor }); } - + function scriptEnding() { Entities.deleteEntity(box); if (soundPlaying) { @@ -93,4 +93,3 @@ function scriptEnding() { // Connect a call back that happens every frame Script.scriptEnding.connect(scriptEnding); Script.update.connect(checkSound); - diff --git a/script-archive/tests/audio/testPeakLimiter.js b/script-archive/tests/audio/testPeakLimiter.js new file mode 100644 index 0000000000..d56126b912 --- /dev/null +++ b/script-archive/tests/audio/testPeakLimiter.js @@ -0,0 +1,19 @@ +var audioOptions = { + volume: 1.0, + loop: true, + position: MyAvatar.position +} + +//var sineWave = Script.resolvePath("./1760sine.wav"); // use relative file +var sineWave = "https://s3-us-west-1.amazonaws.com/highfidelity-dev/1760sine.wav"; // use file from S3 +var sound = SoundCache.getSound(sineWave); +var injectorCount = 0; +var MAX_INJECTOR_COUNT = 40; + +Script.update.connect(function() { + if (sound.downloaded && injectorCount < MAX_INJECTOR_COUNT) { + injectorCount++; + print("stating injector:" + injectorCount); + Audio.playSound(sound, audioOptions); + } +}); \ No newline at end of file diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 2a050d183e..6880de99b5 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -12,12 +12,18 @@ Script.load("system/progress.js"); Script.load("system/away.js"); Script.load("system/users.js"); +Script.load("system/mute.js"); +Script.load("system/goto.js"); +Script.load("system/hmd.js"); Script.load("system/examples.js"); Script.load("system/edit.js"); +Script.load("system/ignore.js"); Script.load("system/selectAudioDevice.js"); Script.load("system/notifications.js"); Script.load("system/controllers/handControllerGrab.js"); Script.load("system/controllers/handControllerPointer.js"); Script.load("system/controllers/squeezeHands.js"); Script.load("system/controllers/grab.js"); +Script.load("system/controllers/teleport.js"); Script.load("system/dialTone.js"); +Script.load("system/firstPersonHMD.js"); diff --git a/scripts/developer/tests/entityEditStressTest.js b/scripts/developer/tests/entityEditStressTest.js index 2d3c8ad0e1..8fa06a968d 100644 --- a/scripts/developer/tests/entityEditStressTest.js +++ b/scripts/developer/tests/entityEditStressTest.js @@ -15,161 +15,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var NUM_ENTITIES = 20000; // number of entities to spawn -var ENTITY_SPAWN_LIMIT = 1000; -var ENTITY_SPAWN_INTERVAL = 0.1; +Script.include("./entitySpawnTool.js"); -var UPDATE_INTERVAL = 0.05; // Re-randomize the entity's position every x seconds / ms -var ENTITY_LIFETIME = 30; // Entity timeout (when/if we crash, we need the entities to delete themselves) -var KEEPALIVE_INTERVAL = 5; // Refreshes the timeout every X seconds - -var RADIUS = 5.0; // Spawn within this radius (square) -var Y_OFFSET = 1.5; // Spawn at an offset below the avatar -var TEST_ENTITY_NAME = "EntitySpawnTest"; - -(function () { - this.makeEntity = function (properties) { - var entity = Entities.addEntity(properties); - // print("spawning entity: " + JSON.stringify(properties)); - - return { - update: function (properties) { - Entities.editEntity(entity, properties); - }, - destroy: function () { - Entities.deleteEntity(entity) - }, - getAge: function () { - return Entities.getEntityProperties(entity).age; - } - }; - } - - this.randomPositionXZ = function (center, radius) { - return { - x: center.x + (Math.random() * radius * 2.0) - radius, - y: center.y, - z: center.z + (Math.random() * radius * 2.0) - radius - }; - } - this.randomColor = function () { - var shade = Math.floor(Math.random() * 255); - var hue = Math.floor(Math.random() * (255 - shade)); - - return { - red: shade + hue, - green: shade, - blue: shade - }; - } - this.randomDimensions = function () { - return { - x: 0.1 + Math.random() * 0.5, - y: 0.1 + Math.random() * 0.1, - z: 0.1 + Math.random() * 0.5 - }; - } -})(); - -(function () { - var entities = []; - var entitiesToCreate = 0; - var entitiesSpawned = 0; - - - function clear () { - var ids = Entities.findEntities(MyAvatar.position, 50); - var that = this; - ids.forEach(function(id) { - var properties = Entities.getEntityProperties(id); - if (properties.name == TEST_ENTITY_NAME) { - Entities.deleteEntity(id); - } - }, this); - } - - function createEntities () { - print("Creating " + NUM_ENTITIES + " entities (UPDATE_INTERVAL = " + UPDATE_INTERVAL + ", KEEPALIVE_INTERVAL = " + KEEPALIVE_INTERVAL + ")"); - entitiesToCreate = NUM_ENTITIES; - Script.update.connect(spawnEntities); - } - - var spawnTimer = 0.0; - function spawnEntities (dt) { - if (entitiesToCreate <= 0) { - Script.update.disconnect(spawnEntities); - print("Finished spawning entities"); - } - else if ((spawnTimer -= dt) < 0.0){ - spawnTimer = ENTITY_SPAWN_INTERVAL; - - var n = Math.min(entitiesToCreate, ENTITY_SPAWN_LIMIT); - print("Spawning " + n + " entities (" + (entitiesSpawned += n) + ")"); - - entitiesToCreate -= n; - - var center = MyAvatar.position; - center.y -= Y_OFFSET; - - for (; n > 0; --n) { - entities.push(makeEntity({ - type: "Box", - name: TEST_ENTITY_NAME, - position: randomPositionXZ(center, RADIUS), - color: randomColor(), - dimensions: randomDimensions(), - lifetime: ENTITY_LIFETIME - })); - } - } - } - - function despawnEntities () { - print("despawning entities"); - entities.forEach(function (entity) { - entity.destroy(); - }); - entities = []; - } - - var keepAliveTimer = 0.0; - var updateTimer = 0.0; - - // Runs the following entity updates: - // a) refreshes the timeout interval every KEEPALIVE_INTERVAL seconds, and - // b) re-randomizes its position every UPDATE_INTERVAL seconds. - // This should be sufficient to crash the client until the entity tree bug is fixed (and thereafter if it shows up again). - function updateEntities (dt) { - var updateLifetime = ((keepAliveTimer -= dt) < 0.0) ? ((keepAliveTimer = KEEPALIVE_INTERVAL), true) : false; - var updateProperties = ((updateTimer -= dt) < 0.0) ? ((updateTimer = UPDATE_INTERVAL), true) : false; - - if (updateLifetime || updateProperties) { - var center = MyAvatar.position; - center.y -= Y_OFFSET; - - entities.forEach((updateLifetime && updateProperties && function (entity) { - entity.update({ - lifetime: entity.getAge() + ENTITY_LIFETIME, - position: randomPositionXZ(center, RADIUS) - }); - }) || (updateLifetime && function (entity) { - entity.update({ - lifetime: entity.getAge() + ENTITY_LIFETIME - }); - }) || (updateProperties && function (entity) { - entity.update({ - position: randomPositionXZ(center, RADIUS) - }); - }) || null, this); - } - } - - function init () { - Script.update.disconnect(init); - clear(); - createEntities(); - Script.update.connect(updateEntities); - Script.scriptEnding.connect(despawnEntities); - } - Script.update.connect(init); -})(); \ No newline at end of file +ENTITY_SPAWNER({ + count: 20000, + spawnLimit: 1000, + spawnInterval: 0.1, + updateInterval: 0.05 +}); diff --git a/scripts/developer/tests/entitySpawnTool.js b/scripts/developer/tests/entitySpawnTool.js new file mode 100644 index 0000000000..d88933b867 --- /dev/null +++ b/scripts/developer/tests/entitySpawnTool.js @@ -0,0 +1,184 @@ +// entityEditStressTest.js +// +// Created by Seiji Emery on 8/31/15 +// Copyright 2015 High Fidelity, Inc. +// +// Stress tests the client + server-side entity trees by spawning huge numbers of entities in +// close proximity to your avatar and updating them continuously (ie. applying position edits), +// with the intent of discovering crashes and other bugs related to the entity, scripting, +// rendering, networking, and/or physics subsystems. +// +// This script was originally created to find + diagnose an a clientside crash caused by improper +// locking of the entity tree, but can be reused for other purposes. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +ENTITY_SPAWNER = function (properties) { + properties = properties || {}; + var RADIUS = properties.radius || 5.0; // Spawn within this radius (square) + var Y_OFFSET = properties.yOffset || 1.5; // Spawn at an offset below the avatar + var TEST_ENTITY_NAME = properties.entityName || "EntitySpawnTest"; + + var NUM_ENTITIES = properties.count || 1000; // number of entities to spawn + var ENTITY_SPAWN_LIMIT = properties.spawnLimit || 100; + var ENTITY_SPAWN_INTERVAL = properties.spawnInterval || properties.interval || 1.0; + + var UPDATE_INTERVAL = properties.updateInterval || properties.interval || 0.1; // Re-randomize the entity's position every x seconds / ms + var ENTITY_LIFETIME = properties.lifetime || 30; // Entity timeout (when/if we crash, we need the entities to delete themselves) + var KEEPALIVE_INTERVAL = properties.keepAlive || 5; // Refreshes the timeout every X seconds + var UPDATES = properties.updates || false + var SHAPES = properties.shapes || ["Icosahedron", "Tetrahedron", "Cube", "Sphere" ]; + + function makeEntity(properties) { + var entity = Entities.addEntity(properties); + // print("spawning entity: " + JSON.stringify(properties)); + + return { + update: function (properties) { + Entities.editEntity(entity, properties); + }, + destroy: function () { + Entities.deleteEntity(entity) + }, + getAge: function () { + return Entities.getEntityProperties(entity).age; + } + }; + } + + function randomPositionXZ(center, radius) { + return { + x: center.x + (Math.random() * radius * 2.0) - radius, + y: center.y, + z: center.z + (Math.random() * radius * 2.0) - radius + }; + } + + function randomPosition(center, radius) { + return { + x: center.x + (Math.random() * radius * 2.0) - radius, + y: center.y + (Math.random() * radius * 2.0) - radius, + z: center.z + (Math.random() * radius * 2.0) - radius + }; + } + + + function randomColor() { + return { + red: Math.floor(Math.random() * 255), + green: Math.floor(Math.random() * 255), + blue: Math.floor(Math.random() * 255), + }; + } + + function randomDimensions() { + return { + x: 0.1 + Math.random() * 0.5, + y: 0.1 + Math.random() * 0.1, + z: 0.1 + Math.random() * 0.5 + }; + } + + var entities = []; + var entitiesToCreate = 0; + var entitiesSpawned = 0; + var spawnTimer = 0.0; + var keepAliveTimer = 0.0; + var updateTimer = 0.0; + + function clear () { + var ids = Entities.findEntities(MyAvatar.position, 50); + var that = this; + ids.forEach(function(id) { + var properties = Entities.getEntityProperties(id); + if (properties.name == TEST_ENTITY_NAME) { + Entities.deleteEntity(id); + } + }, this); + } + + function createEntities () { + print("Creating " + NUM_ENTITIES + " entities (UPDATE_INTERVAL = " + UPDATE_INTERVAL + ", KEEPALIVE_INTERVAL = " + KEEPALIVE_INTERVAL + ")"); + entitiesToCreate = NUM_ENTITIES; + Script.update.connect(spawnEntities); + } + + function spawnEntities (dt) { + if (entitiesToCreate <= 0) { + Script.update.disconnect(spawnEntities); + print("Finished spawning entities"); + } + else if ((spawnTimer -= dt) < 0.0){ + spawnTimer = ENTITY_SPAWN_INTERVAL; + + var n = Math.min(entitiesToCreate, ENTITY_SPAWN_LIMIT); + print("Spawning " + n + " entities (" + (entitiesSpawned += n) + ")"); + + entitiesToCreate -= n; + + var center = MyAvatar.position; + center.y -= Y_OFFSET; + + for (; n > 0; --n) { + entities.push(makeEntity({ + type: "Shape", + shape: SHAPES[n % SHAPES.length], + name: TEST_ENTITY_NAME, + position: randomPosition(center, RADIUS), + color: randomColor(), + dimensions: randomDimensions(), + lifetime: ENTITY_LIFETIME + })); + } + } + } + + function despawnEntities () { + print("despawning entities"); + entities.forEach(function (entity) { + entity.destroy(); + }); + entities = []; + } + + // Runs the following entity updates: + // a) refreshes the timeout interval every KEEPALIVE_INTERVAL seconds, and + // b) re-randomizes its position every UPDATE_INTERVAL seconds. + // This should be sufficient to crash the client until the entity tree bug is fixed (and thereafter if it shows up again). + function updateEntities (dt) { + var updateLifetime = ((keepAliveTimer -= dt) < 0.0) ? ((keepAliveTimer = KEEPALIVE_INTERVAL), true) : false; + var updateProperties = ((updateTimer -= dt) < 0.0) ? ((updateTimer = UPDATE_INTERVAL), true) : false; + + if (updateLifetime || updateProperties) { + var center = MyAvatar.position; + center.y -= Y_OFFSET; + + entities.forEach((updateLifetime && updateProperties && function (entity) { + entity.update({ + lifetime: entity.getAge() + ENTITY_LIFETIME, + position: randomPosition(center, RADIUS) + }); + }) || (updateLifetime && function (entity) { + entity.update({ + lifetime: entity.getAge() + ENTITY_LIFETIME + }); + }) || (updateProperties && function (entity) { + entity.update({ + position: randomPosition(center, RADIUS) + }); + }) || null, this); + } + } + + function init () { + Script.update.disconnect(init); + clear(); + createEntities(); + Script.update.connect(updateEntities); + Script.scriptEnding.connect(despawnEntities); + } + + Script.update.connect(init); +}; \ No newline at end of file diff --git a/scripts/developer/tests/loadedMachine.js b/scripts/developer/tests/loadedMachine.js new file mode 100644 index 0000000000..375e3d8004 --- /dev/null +++ b/scripts/developer/tests/loadedMachine.js @@ -0,0 +1,192 @@ +"use strict"; +/*jslint vars: true, plusplus: true*/ +/*globals Script, MyAvatar, Quat, Render, ScriptDiscoveryService, Window, LODManager, Entities, print*/ +// +// loadedMachine.js +// scripts/developer/tests/ +// +// Created by Howard Stearns on 6/6/16. +// Copyright 2016 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 +// +// Characterises the performance of heavily loaded or struggling machines. +// There is no point in running this on a machine that producing the target 60 or 90 hz rendering rate. +// +// The script examines several source of load, including each running script and a couple of Entity types. +// It turns all of these off, as well as LOD management, and twirls in place to get a baseline render rate. +// Then it turns each load on, one at a time, and records a new render rate. +// At the end, it reports the ordered results (in a popup), restores the original loads and LOD management, and quits. +// +// Small performance changes are not meaningful. +// If you think you find something that is significant, you should probably run again to make sure that it isn't random variation. +// You can also compare (e.g., the baseline) for different conditions such as domain, version, server settings, etc. +// This does not profile scripts as they are used -- it merely measures the effect of having the script loaded and running quietly. + +var NUMBER_OF_TWIRLS_PER_LOAD = 10; +var MILLISECONDS_PER_SECOND = 1000; +var WAIT_TIME_MILLISECONDS = MILLISECONDS_PER_SECOND; +var accumulatedRotation = 0; +var start; +var frames = 0; +var config = Render.getConfig("Stats"); +var thisPath = Script.resolvePath(''); +var scriptData = ScriptDiscoveryService.getRunning(); +var scripts = scriptData.filter(function (datum) { return datum.name !== 'defaultScripts.js'; }).map(function (script) { return script.path; }); +// If defaultScripts.js is running, we leave it running, because restarting it safely is a mess. +var otherScripts = scripts.filter(function (path) { return path !== thisPath; }); +var numberLeftRunning = scriptData.length - otherScripts.length; +print('initially running', otherScripts.length, 'scripts. Leaving', numberLeftRunning, 'and stopping', otherScripts); +var typedEntities = {Light: [], ParticleEffect: []}; +var interestingTypes = Object.keys(typedEntities); +var propertiedEntities = {dynamic: []}; +var interestingProperties = Object.keys(propertiedEntities); +var loads = ['ignore', 'baseline'].concat(otherScripts, interestingTypes, interestingProperties); +var loadIndex = 0, nLoads = loads.length, load; +var results = []; +var initialLodIsAutomatic = LODManager.getAutomaticLODAdjust(); +var SEARCH_RADIUS = 17000; +var DEFAULT_LOD = 32768 * 400; +LODManager.setAutomaticLODAdjust(false); +LODManager.setOctreeSizeScale(DEFAULT_LOD); + +// Fill the typedEnties with the entityIDs that are already visible. It would be nice if this were more efficient. +var allEntities = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS); +print('Searching', allEntities.length, 'entities for', interestingTypes, 'and', interestingProperties); +var propertiesToGet = ['type', 'visible'].concat(interestingProperties); +allEntities.forEach(function (entityID) { + var properties = Entities.getEntityProperties(entityID, propertiesToGet); + if (properties.visible) { + if (interestingTypes.indexOf(properties.type) >= 0) { + typedEntities[properties.type].push(entityID); + } else { + interestingProperties.forEach(function (property) { + if (entityID && properties[property]) { + propertiedEntities[property].push(entityID); + entityID = false; // Put in only one bin + } + }); + } + } +}); +allEntities = undefined; // free them +interestingTypes.forEach(function (type) { + print('There are', typedEntities[type].length, type, 'entities.'); +}); +interestingProperties.forEach(function (property) { + print('There are', propertiedEntities[property].length, property, 'entities.'); +}); +function toggleVisibility(type, on) { + typedEntities[type].forEach(function (entityID) { + Entities.editEntity(entityID, {visible: on}); + }); +} +function toggleProperty(property, on) { + propertiedEntities[property].forEach(function (entityID) { + var properties = {}; + properties[property] = on; + Entities.editEntity(entityID, properties); + }); +} +function restoreOneTest(load) { + print('restore', load); + switch (load) { + case 'baseline': + case 'ignore': // ignore is used to do a twirl to make sure everything is loaded. + break; + case 'Light': + case 'ParticleEffect': + toggleVisibility(load, true); + break; + case 'dynamic': + toggleProperty(load, 1); + break; + default: + Script.load(load); + } +} +function undoOneTest(load) { + print('stop', load); + switch (load) { + case 'baseline': + case 'ignore': + break; + case 'Light': + case 'ParticleEffect': + toggleVisibility(load, false); + break; + case 'dynamic': + toggleProperty(load, 0); + break; + default: + ScriptDiscoveryService.stopScript(load); + } +} +loads.forEach(undoOneTest); + +function finalReport() { + var baseline = results[0]; + results.forEach(function (result) { + result.ratio = (baseline.fps / result.fps); + }); + results.sort(function (a, b) { return b.ratio - a.ratio; }); + var report = 'Performance Report:\nBaseline: ' + baseline.fps.toFixed(1) + ' fps.'; + results.forEach(function (result) { + report += '\n' + result.ratio.toFixed(2) + ' (' + result.fps.toFixed(1) + ' fps over ' + result.elapsed + ' seconds) for ' + result.name; + }); + Window.alert(report); + LODManager.setAutomaticLODAdjust(initialLodIsAutomatic); + loads.forEach(restoreOneTest); + Script.stop(); +} + +function onNewStats() { // Accumulates frames on signal during load test + frames++; +} +var DEGREES_PER_FULL_TWIRL = 360; +var DEGREES_PER_UPDATE = 6; +function onUpdate() { // Spins on update signal during load test + MyAvatar.orientation = Quat.fromPitchYawRollDegrees(0, accumulatedRotation, 0); + accumulatedRotation += DEGREES_PER_UPDATE; + if (accumulatedRotation >= (DEGREES_PER_FULL_TWIRL * NUMBER_OF_TWIRLS_PER_LOAD)) { + tearDown(); + } +} +function startTest() { + print('start', load); + accumulatedRotation = frames = 0; + start = Date.now(); + Script.update.connect(onUpdate); + config.newStats.connect(onNewStats); +} + +function setup() { + if (loadIndex >= nLoads) { + return finalReport(); + } + load = loads[loadIndex]; + restoreOneTest(load); + Script.setTimeout(startTest, WAIT_TIME_MILLISECONDS); +} +function tearDown() { + var now = Date.now(); + var elapsed = (now - start) / MILLISECONDS_PER_SECOND; + Script.update.disconnect(onUpdate); + config.newStats.disconnect(onNewStats); + if (load !== 'ignore') { + results.push({name: load, fps: frames / elapsed, elapsed: elapsed}); + } + undoOneTest(load); + loadIndex++; + setup(); +} +function waitUntilStopped() { // Wait until all the scripts have stopped + var count = ScriptDiscoveryService.getRunning().length; + if (count === numberLeftRunning) { + return setup(); + } + print('Still', count, 'scripts running'); + Script.setTimeout(waitUntilStopped, WAIT_TIME_MILLISECONDS); +} +waitUntilStopped(); diff --git a/scripts/developer/tests/playaPerformanceTest.qml b/scripts/developer/tests/playaPerformanceTest.qml index cd8171a0f6..028cfc9e1a 100644 --- a/scripts/developer/tests/playaPerformanceTest.qml +++ b/scripts/developer/tests/playaPerformanceTest.qml @@ -125,7 +125,6 @@ Rectangle { TextField { id: addressLine - focus: true anchors { left: parent.left; right: parent.right; top: row.bottom; margins: 16; diff --git a/scripts/developer/tests/primitivesTest.js b/scripts/developer/tests/primitivesTest.js new file mode 100644 index 0000000000..e401963a83 --- /dev/null +++ b/scripts/developer/tests/primitivesTest.js @@ -0,0 +1,24 @@ +// entityEditStressTest.js +// +// Created by Seiji Emery on 8/31/15 +// Copyright 2015 High Fidelity, Inc. +// +// Stress tests the client + server-side entity trees by spawning huge numbers of entities in +// close proximity to your avatar and updating them continuously (ie. applying position edits), +// with the intent of discovering crashes and other bugs related to the entity, scripting, +// rendering, networking, and/or physics subsystems. +// +// This script was originally created to find + diagnose an a clientside crash caused by improper +// locking of the entity tree, but can be reused for other purposes. +// +// 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("./entitySpawnTool.js"); + + +ENTITY_SPAWNER({ + updateInterval: 2.0 +}) + diff --git a/scripts/developer/tests/toolbarTest.js b/scripts/developer/tests/toolbarTest.js new file mode 100644 index 0000000000..e21fbd8e19 --- /dev/null +++ b/scripts/developer/tests/toolbarTest.js @@ -0,0 +1,118 @@ +var isActive = false; + +var toolBar = (function() { + var that = {}, + toolBar, + activeButton, + newModelButton, + newCubeButton, + newSphereButton, + newLightButton, + newTextButton, + newWebButton, + newZoneButton, + newParticleButton + + var toolIconUrl = Script.resolvePath("../../system/assets/images/tools/"); + + function initialize() { + print("Toolbars: " + Toolbars); + toolBar = Toolbars.getToolbar("highfidelity.edit.toolbar"); + print("Toolbar: " + toolBar); + activeButton = toolBar.addButton({ + objectName: "activeButton", + imageURL: toolIconUrl + "edit-01.svg", + visible: true, + alpha: 0.9, + }); + + print("Button " + activeButton); + print("Button signal " + activeButton.clicked); + activeButton.clicked.connect(function(){ + print("Clicked on button " + isActive); + that.setActive(!isActive); + }); + + newModelButton = toolBar.addButton({ + objectName: "newModelButton", + imageURL: toolIconUrl + "model-01.svg", + alpha: 0.9, + visible: false + }); + + newCubeButton = toolBar.addButton({ + objectName: "newCubeButton", + imageURL: toolIconUrl + "cube-01.svg", + alpha: 0.9, + visible: false + }); + + newSphereButton = toolBar.addButton({ + objectName: "newSphereButton", + imageURL: toolIconUrl + "sphere-01.svg", + alpha: 0.9, + visible: false + }); + + newLightButton = toolBar.addButton({ + objectName: "newLightButton", + imageURL: toolIconUrl + "light-01.svg", + alpha: 0.9, + visible: false + }); + + newTextButton = toolBar.addButton({ + objectName: "newTextButton", + imageURL: toolIconUrl + "text-01.svg", + alpha: 0.9, + visible: false + }); + + newWebButton = toolBar.addButton({ + objectName: "newWebButton", + imageURL: toolIconUrl + "web-01.svg", + alpha: 0.9, + visible: false + }); + + newZoneButton = toolBar.addButton({ + objectName: "newZoneButton", + imageURL: toolIconUrl + "zone-01.svg", + alpha: 0.9, + visible: false + }); + + newParticleButton = toolBar.addButton({ + objectName: "newParticleButton", + imageURL: toolIconUrl + "particle-01.svg", + alpha: 0.9, + visible: false + }); + + that.setActive(false); + newModelButton.clicked(); + } + + that.setActive = function(active) { + if (active != isActive) { + isActive = active; + that.showTools(isActive); + } + }; + + // Sets visibility of tool buttons, excluding the power button + that.showTools = function(doShow) { + newModelButton.writeProperty('visible', doShow); + newCubeButton.writeProperty('visible', doShow); + newSphereButton.writeProperty('visible', doShow); + newLightButton.writeProperty('visible', doShow); + newTextButton.writeProperty('visible', doShow); + newWebButton.writeProperty('visible', doShow); + newZoneButton.writeProperty('visible', doShow); + newModelButton.writeProperty('visible', doShow); + newParticleButton.writeProperty('visible', doShow); + }; + + initialize(); + return that; +}()); diff --git a/scripts/developer/utilities/diagnostics/typedArraysUnitTest.js b/scripts/developer/utilities/diagnostics/typedArraysUnitTest.js index a50a40e43e..c61865c8d6 100644 --- a/scripts/developer/utilities/diagnostics/typedArraysUnitTest.js +++ b/scripts/developer/utilities/diagnostics/typedArraysUnitTest.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/unitTest.js"); +Script.include("../../../../script-archive/libraries/unitTest.js"); // e.g. extractbits([0xff, 0x80, 0x00, 0x00], 23, 30); inclusive function extractbits(bytes, lo, hi) { @@ -551,6 +551,20 @@ test('TypedArray.subarray', function () { this.arrayEqual(a.subarray(-1, -4), []); this.arrayEqual(a.subarray(1).subarray(1), [3, 4, 5]); this.arrayEqual(a.subarray(1, 4).subarray(1, 2), [3]); + + var a = new Float32Array(16); + a[0] = -1; + a[15] = 1/8; + this.assertEquals(a.length, 16); + this.assertEquals(a.byteLength, a.length * a.BYTES_PER_ELEMENT); + + this.assertEquals(a.subarray(-12).length, 12, '[-12,)'); + this.arrayEqual(a.subarray(-16), a, '[-16,)'); + this.arrayEqual(a.subarray(12, 16), [0,0,0,1/8], '[12,16)'); + this.arrayEqual(a.subarray(0, 4), [-1,0,0,0],'[0,4)'); + this.arrayEqual(a.subarray(-16, -12), [-1,0,0,0],'[-16,-12)'); + this.assertEquals(a.subarray(0, -12).length, 4,'[0,-12)'); + this.arrayEqual(a.subarray(-10), a.subarray(6),'[-10,)'); }); @@ -706,3 +720,45 @@ test('Regression Tests', function() { this.assertEquals(truncated, -minFloat32, 'smallest 32 bit float should not truncate to zero'); }); +test('new TypedArray(ArrayBuffer).length Tests', function() { + var uint8s = new Uint8Array([0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff]), + buffer = uint8s.buffer; + + this.assertEquals(buffer.byteLength, 8, 'buffer.length'); + + var _this = this; + [ + 'Uint8Array', 'Uint16Array', 'Uint32Array', 'Int8Array', 'Int16Array', 'Int32Array', + 'Float32Array', 'Float64Array', 'Uint8ClampedArray' + ].forEach(function(typeArrayName) { + var typeArray = eval(typeArrayName); + var a = new typeArray(buffer); + _this.assertEquals(a.BYTES_PER_ELEMENT, typeArrayName.match(/\d+/)[0]/8, typeArrayName+'.BYTES_PER_ELEMENT'); + _this.assertEquals(a.byteLength, buffer.byteLength, typeArrayName+'.byteLength'); + _this.assertEquals(a.length, buffer.byteLength / typeArray.BYTES_PER_ELEMENT, typeArrayName+'.length'); + }); +}); + +test('Native endianness check', function() { + var buffer = ArrayBuffer(4); + new Uint8Array(buffer).set([0xaa, 0xbb, 0xcc, 0xdd]); + var endian = { aabbccdd: 'big', ddccbbaa: 'little' }[ + new Uint32Array(buffer)[0].toString(16) + ]; + this.assertEquals(endian, 'little'); +}); + +test('TypeArray byte order tests', function() { + var uint8s = new Uint8Array([0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff]), + buffer = uint8s.buffer; + + this.arrayEqual(new Uint8Array(buffer), [0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff], "Uint8Array"); + this.arrayEqual(new Uint16Array(buffer), [0x00ff, 0x0000, 0x0000, 0xff00], "Uint16Array"); + this.arrayEqual(new Uint32Array(buffer), [0x000000ff, 0xff000000], "Uint32Array"); + + this.arrayEqual(new Int8Array(buffer), [-1,0,0,0,0,0,0,-1], "Int8Array"); + this.arrayEqual(new Int16Array(buffer), [255, 0, 0, -256], "Int16Array"); + + this.arrayEqual(new Float32Array(buffer), [3.5733110840282835e-43, -1.7014118346046923e+38], "Float32Array"); + this.arrayEqual(new Float64Array(buffer), [-5.486124068793999e+303], "Float64Array"); +}); diff --git a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml index 6871ffe6a6..c6b95dca30 100644 --- a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml +++ b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml @@ -16,6 +16,8 @@ Item { width: parent.width height: 100 + property int hitboxExtension : 20 + // The title of the graph property string title @@ -71,6 +73,11 @@ Item { Component.onCompleted: { createValues(); } + function resetMax() { + for (var i = 0; i < _values.length; i++) { + _values[i].valueMax *= 0.25 // Fast reduce the max value as we click + } + } function pullFreshValues() { // Wait until values are created to begin pulling @@ -125,6 +132,7 @@ Item { Canvas { id: mycanvas anchors.fill:parent + onPaint: { var lineHeight = 12; @@ -199,4 +207,19 @@ Item { displayTitle(ctx, title, valueMax) } } + + MouseArea { + id: hitbox + anchors.fill:mycanvas + + anchors.topMargin: -hitboxExtension + anchors.bottomMargin: -hitboxExtension + anchors.leftMargin: -hitboxExtension + anchors.rightMargin: -hitboxExtension + + onClicked: { + print("PerfPlot clicked!") + resetMax(); + } + } } diff --git a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml index 5ee62dfe49..996cf4b34c 100644 --- a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml +++ b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml @@ -40,7 +40,7 @@ Item { Label { text: sliderControl.value.toFixed(root.integral ? 0 : 2) anchors.left: root.left - anchors.leftMargin: 140 + anchors.leftMargin: 200 anchors.top: root.top anchors.topMargin: 7 } @@ -56,7 +56,7 @@ Item { Slider { id: sliderControl stepSize: root.integral ? 1.0 : 0.0 - width: 192 + width: 150 height: 20 anchors.right: root.right anchors.rightMargin: 8 diff --git a/scripts/developer/utilities/render/currentZone.js b/scripts/developer/utilities/render/currentZone.js new file mode 100644 index 0000000000..28de78b80f --- /dev/null +++ b/scripts/developer/utilities/render/currentZone.js @@ -0,0 +1,73 @@ +// +// currentZone.js +// examples/utilities/tools/render +// +// Sam Gateau created on 6/18/2016. +// Copyright 2016 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 +// + +// Set up the qml ui +/*var qml = Script.resolvePath('framebuffer.qml'); +var window = new OverlayWindow({ + title: 'Framebuffer Debug', + source: qml, + width: 400, height: 400, +}); +window.setPosition(25, 50); +window.closed.connect(function() { Script.stop(); }); +*/ + + + +function findCurrentZones() { + var foundEntitiesArray = Entities.findEntities(MyAvatar.position, 2.0); + //print(foundEntitiesArray.length); + var zones = []; + + foundEntitiesArray.forEach(function(foundID){ + var properties = Entities.getEntityProperties(foundID); + if (properties.type == "Zone") { + zones.push(foundID); + } + }); + return zones; +} + + +var currentZone; +var currentZoneProperties; + +function setCurrentZone(newCurrentZone) { + if (currentZone == newCurrentZone) { + return; + } + + currentZone = newCurrentZone; + currentZoneProperties = Entities.getEntityProperties(currentZone); + + print(JSON.stringify(currentZoneProperties)); +} + +var checkCurrentZone = function() { + + var currentZones = findCurrentZones(); + if (currentZones.length > 0) { + if (currentZone != currentZones[0]) { + print("New Zone"); + setCurrentZone(currentZones[0]); + } + } + +} +var ticker = Script.setInterval(checkCurrentZone, 2000); + +//checkCurrentZone(); + +function onQuit() { + Script.clearInterval(ticker); + print("Quit Zone"); +} +Script.scriptEnding.connect(onQuit); diff --git a/scripts/developer/utilities/render/debugDeferredLighting.js b/scripts/developer/utilities/render/debugDeferredLighting.js new file mode 100644 index 0000000000..b32a924c10 --- /dev/null +++ b/scripts/developer/utilities/render/debugDeferredLighting.js @@ -0,0 +1,49 @@ +// +// debugDeferredLighting.js +// +// Created by Sam Gateau on 6/6/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +// Set up the qml ui +var qml = Script.resolvePath('deferredLighting.qml'); +var window = new OverlayWindow({ + title: 'Lighting', + source: qml, + width: 400, height:220, +}); +window.setPosition(Window.innerWidth - 420, 50); +window.closed.connect(function() { Script.stop(); }); + + +var DDB = Render.RenderDeferredTask.DebugDeferredBuffer; +DDB.enabled = true; +DDB.mode = 0; + +// Debug buffer sizing +var resizing = false; +Controller.mousePressEvent.connect(function (e) { + if (shouldStartResizing(e.x)) { + resizing = true; + } +}); +Controller.mouseReleaseEvent.connect(function() { resizing = false; }); +Controller.mouseMoveEvent.connect(function (e) { resizing && setDebugBufferSize(e.x); }); +Script.scriptEnding.connect(function () { DDB.enabled = false; }); + +function shouldStartResizing(eventX) { + var x = Math.abs(eventX - Window.innerWidth * (1.0 + DDB.size.x) / 2.0); + var mode = DDB.mode; + return mode !== 0 && x < 20; +} + +function setDebugBufferSize(x) { + x = (2.0 * (x / Window.innerWidth) - 1.0); // scale + x = Math.min(Math.max(-1, x), 1); // clamp + DDB.size = { x: x, y: -1, z: 1, w: 1 }; +} + + diff --git a/scripts/developer/utilities/render/debugFramebuffer.js b/scripts/developer/utilities/render/debugFramebuffer.js index e764cf52d8..12a19085c8 100644 --- a/scripts/developer/utilities/render/debugFramebuffer.js +++ b/scripts/developer/utilities/render/debugFramebuffer.js @@ -19,7 +19,7 @@ var qml = Script.resolvePath('framebuffer.qml'); var window = new OverlayWindow({ title: 'Framebuffer Debug', source: qml, - width: 400, height: 400, + width: 400, height: 50, }); window.setPosition(25, 50); window.closed.connect(function() { Script.stop(); }); diff --git a/scripts/developer/utilities/render/debugSubsurfaceScattering.js b/scripts/developer/utilities/render/debugSubsurfaceScattering.js new file mode 100644 index 0000000000..72b15546e0 --- /dev/null +++ b/scripts/developer/utilities/render/debugSubsurfaceScattering.js @@ -0,0 +1,37 @@ +// +// debugSubsirfaceScattering.js +// +// Created by Sam Gateau on 6/6/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +// Set up the qml ui +var qml = Script.resolvePath('subsurfaceScattering.qml'); +var window = new OverlayWindow({ + title: 'Subsurface Scattering', + source: qml, + width: 400, height: 350, +}); +window.setPosition(250, 950); +window.closed.connect(function() { Script.stop(); }); + +var moveDebugCursor = false; +Controller.mousePressEvent.connect(function (e) { + if (e.isMiddleButton) { + moveDebugCursor = true; + setDebugCursor(e.x, e.y); + } +}); +Controller.mouseReleaseEvent.connect(function() { moveDebugCursor = false; }); +Controller.mouseMoveEvent.connect(function (e) { if (moveDebugCursor) setDebugCursor(e.x, e.y); }); + + +function setDebugCursor(x, y) { + nx = (x / Window.innerWidth); + ny = 1.0 - ((y) / (Window.innerHeight - 32)); + + Render.getConfig("DebugScattering").debugCursorTexcoord = { x: nx, y: ny }; +} diff --git a/scripts/developer/utilities/render/debugSurfaceGeometryPass.js b/scripts/developer/utilities/render/debugSurfaceGeometryPass.js new file mode 100644 index 0000000000..44df10e690 --- /dev/null +++ b/scripts/developer/utilities/render/debugSurfaceGeometryPass.js @@ -0,0 +1,19 @@ +// +// debugSurfaceGeometryPass.js +// +// Created by Sam Gateau on 6/6/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +// Set up the qml ui +var qml = Script.resolvePath('surfaceGeometryPass.qml'); +var window = new OverlayWindow({ + title: 'Surface Geometry Pass', + source: qml, + width: 400, height: 170, +}); +window.setPosition(Window.innerWidth - 420, 50 + 550 + 50); +window.closed.connect(function() { Script.stop(); }); diff --git a/scripts/developer/utilities/render/debugToneMapping.js b/scripts/developer/utilities/render/debugToneMapping.js new file mode 100644 index 0000000000..ef14c24fb7 --- /dev/null +++ b/scripts/developer/utilities/render/debugToneMapping.js @@ -0,0 +1,20 @@ +// +// debugToneMapping.js +// +// Created by Sam Gateau on 6/30/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +// Set up the qml ui +var qml = Script.resolvePath('toneMapping.qml'); +var window = new OverlayWindow({ + title: 'Tone Mapping', + source: qml, + width: 400, height: 200, +}); +window.setPosition(250, 1000); +window.closed.connect(function() { Script.stop(); }); + diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml new file mode 100644 index 0000000000..635d8b1471 --- /dev/null +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -0,0 +1,159 @@ +// +// deferredLighting.qml +// +// Created by Sam Gateau on 6/6/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "configSlider" + +Column { + spacing: 8 + + Row { + spacing: 8 + Column { + spacing: 10 + Repeater { + model: [ + "Unlit:LightingModel:enableUnlit", + "Emissive:LightingModel:enableEmissive", + "Lightmap:LightingModel:enableLightmap", + "Background:LightingModel:enableBackground", + ] + CheckBox { + text: modelData.split(":")[0] + checked: Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] + onCheckedChanged: { Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } + } + } + } + + + Column { + spacing: 10 + Repeater { + model: [ + "Obscurance:LightingModel:enableObscurance", + "Scattering:LightingModel:enableScattering", + "Diffuse:LightingModel:enableDiffuse", + "Specular:LightingModel:enableSpecular", + "Albedo:LightingModel:enableAlbedo", + ] + CheckBox { + text: modelData.split(":")[0] + checked: Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] + onCheckedChanged: { Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } + } + } + } + + Column { + spacing: 10 + Repeater { + model: [ + "Ambient:LightingModel:enableAmbientLight", + "Directional:LightingModel:enableDirectionalLight", + "Point:LightingModel:enablePointLight", + "Spot:LightingModel:enableSpotLight", + "Light Contour:LightingModel:showLightContour" + ] + CheckBox { + text: modelData.split(":")[0] + checked: Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] + onCheckedChanged: { Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } + } + } + } + } + Column { + spacing: 10 + Repeater { + model: [ "Tone Mapping exposure:ToneMapping:exposure:5.0:-5.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: Render.getConfig(modelData.split(":")[1]) + property: modelData.split(":")[2] + max: modelData.split(":")[3] + min: modelData.split(":")[4] + } + } + + Row { + Label { + text: "Debug Framebuffer" + anchors.left: root.left + } + + ComboBox { + anchors.right: root.right + currentIndex: 1 + model: ListModel { + id: cbItems + ListElement { text: "RGB"; color: "Yellow" } + ListElement { text: "SRGB"; color: "Green" } + ListElement { text: "Reinhard"; color: "Yellow" } + ListElement { text: "Filmic"; color: "White" } + } + width: 200 + onCurrentIndexChanged: { Render.getConfig("ToneMapping")["curve"] = currentIndex } + } + } + } + Row { + id: framebuffer + + Label { + text: "Debug Framebuffer" + anchors.left: root.left + } + + property var config: Render.getConfig("DebugDeferredBuffer") + + function setDebugMode(mode) { + framebuffer.config.enabled = (mode != 0); + framebuffer.config.mode = mode; + } + + ComboBox { + anchors.right: root.right + currentIndex: 0 + model: ListModel { + id: cbItemsFramebuffer + ListElement { text: "Off"; color: "Yellow" } + ListElement { text: "Depth"; color: "Green" } + ListElement { text: "Albedo"; color: "Yellow" } + ListElement { text: "Normal"; color: "White" } + ListElement { text: "Roughness"; color: "White" } + ListElement { text: "Metallic"; color: "White" } + ListElement { text: "Emissive"; color: "White" } + ListElement { text: "Unlit"; color: "White" } + ListElement { text: "Occlusion"; color: "White" } + ListElement { text: "Lightmap"; color: "White" } + ListElement { text: "Scattering"; color: "White" } + ListElement { text: "Lighting"; color: "White" } + ListElement { text: "Shadow"; color: "White" } + ListElement { text: "Linear Depth"; color: "White" } + ListElement { text: "Half Linear Depth"; color: "White" } + ListElement { text: "Half Normal"; color: "White" } + ListElement { text: "Mid Curvature"; color: "White" } + ListElement { text: "Mid Normal"; color: "White" } + ListElement { text: "Low Curvature"; color: "White" } + ListElement { text: "Low Normal"; color: "White" } + ListElement { text: "Debug Scattering"; color: "White" } + ListElement { text: "Ambient Occlusion"; color: "White" } + ListElement { text: "Ambient Occlusion Blurred"; color: "White" } + ListElement { text: "Custom"; color: "White" } + } + width: 200 + onCurrentIndexChanged: { framebuffer.setDebugMode(currentIndex) } + } + } +} + diff --git a/scripts/developer/utilities/render/framebuffer.qml b/scripts/developer/utilities/render/framebuffer.qml index 0d8d85cc32..65046106dc 100644 --- a/scripts/developer/utilities/render/framebuffer.qml +++ b/scripts/developer/utilities/render/framebuffer.qml @@ -10,6 +10,7 @@ // import QtQuick 2.5 import QtQuick.Controls 1.4 +import "configSlider" Column { spacing: 8 @@ -22,33 +23,35 @@ Column { debug.config.mode = mode; } - Label { text: qsTr("Debug Buffer") } - ExclusiveGroup { id: bufferGroup } - Repeater { - model: [ - "Off", - "Depth", - "Albedo", - "Normal", - "Roughness", - "Metallic", - "Emissive", - "Shaded/Lightmapped/Unlit", - "Occlusion", - "Lightmap", - "Lighting", - "Shadow", - "Pyramid Depth", - "Ambient Occlusion", - "Ambient Occlusion Blurred", - "Custom Shader" - ] - RadioButton { - text: qsTr(modelData) - exclusiveGroup: bufferGroup - checked: index == 0 - onCheckedChanged: if (checked) debug.setDebugMode(index - 1); + ComboBox { + currentIndex: 0 + model: ListModel { + id: cbItems + ListElement { text: "Off"; color: "Yellow" } + ListElement { text: "Depth"; color: "Green" } + ListElement { text: "Albedo"; color: "Yellow" } + ListElement { text: "Normal"; color: "White" } + ListElement { text: "Roughness"; color: "White" } + ListElement { text: "Metallic"; color: "White" } + ListElement { text: "Emissive"; color: "White" } + ListElement { text: "Unlit"; color: "White" } + ListElement { text: "Occlusion"; color: "White" } + ListElement { text: "Lightmap"; color: "White" } + ListElement { text: "Scattering"; color: "White" } + ListElement { text: "Lighting"; color: "White" } + ListElement { text: "Shadow"; color: "White" } + ListElement { text: "Linear Depth"; color: "White" } + ListElement { text: "Mid Curvature"; color: "White" } + ListElement { text: "Mid Normal"; color: "White" } + ListElement { text: "Low Curvature"; color: "White" } + ListElement { text: "Low Normal"; color: "White" } + ListElement { text: "Debug Scattering"; color: "White" } + ListElement { text: "Ambient Occlusion"; color: "White" } + ListElement { text: "Ambient Occlusion Blurred"; color: "White" } + ListElement { text: "Custom"; color: "White" } } + width: 200 + onCurrentIndexChanged: { debug.setDebugMode(currentIndex - 1) } } } } diff --git a/scripts/developer/utilities/render/globalLight.qml b/scripts/developer/utilities/render/globalLight.qml new file mode 100644 index 0000000000..ac0d7ebcd5 --- /dev/null +++ b/scripts/developer/utilities/render/globalLight.qml @@ -0,0 +1,36 @@ +// +// globalLight.qml +// examples/utilities/render +// +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "configSlider" + +Column { + id: root + spacing: 8 + property var currentZoneID + property var zoneProperties + + Component.onCompleted: { + Entities.getProperties + sceneOctree.enabled = true; + itemSelection.enabled = true; + sceneOctree.showVisibleCells = false; + sceneOctree.showEmptyCells = false; + itemSelection.showInsideItems = false; + itemSelection.showInsideSubcellItems = false; + itemSelection.showPartialItems = false; + itemSelection.showPartialSubcellItems = false; + } + Component.onDestruction: { + sceneOctree.enabled = false; + itemSelection.enabled = false; + Render.getConfig("FetchSceneSelection").freezeFrustum = false; + Render.getConfig("CullSceneSelection").freezeFrustum = false; + } diff --git a/scripts/developer/utilities/render/renderStatsGPU.js b/scripts/developer/utilities/render/renderStatsGPU.js new file mode 100644 index 0000000000..fc35cb053a --- /dev/null +++ b/scripts/developer/utilities/render/renderStatsGPU.js @@ -0,0 +1,21 @@ +// +// renderStats.js +// examples/utilities/tools/render +// +// Sam Gateau, created on 3/22/2016. +// Copyright 2016 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 +// + +// Set up the qml ui +var qml = Script.resolvePath('statsGPU.qml'); +var window = new OverlayWindow({ + title: 'Render Stats GPU', + source: qml, + width: 400, + height: 200 +}); +window.setPosition(Window.innerWidth - 420, 50 + 250 + 50); +window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/scripts/developer/utilities/render/stats.qml b/scripts/developer/utilities/render/stats.qml index a71b80080e..1fe1a4d031 100644 --- a/scripts/developer/utilities/render/stats.qml +++ b/scripts/developer/utilities/render/stats.qml @@ -246,7 +246,7 @@ Item { color: "#E2334D" } ] - } + } } } diff --git a/scripts/developer/utilities/render/statsGPU.qml b/scripts/developer/utilities/render/statsGPU.qml new file mode 100644 index 0000000000..74bd376a00 --- /dev/null +++ b/scripts/developer/utilities/render/statsGPU.qml @@ -0,0 +1,75 @@ +// +// stats.qml +// examples/utilities/render +// +// Created by Zach Pomerantz on 2/8/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../lib/plotperf" + +Item { + id: statsUI + anchors.fill:parent + + Column { + id: stats + spacing: 8 + anchors.fill:parent + + property var config: Render.getConfig("Stats") + + function evalEvenHeight() { + // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? + return (height - spacing * (children.length - 1)) / children.length + } + + + PlotPerf { + title: "Timing" + height: parent.evalEvenHeight() + object: parent.drawOpaqueConfig + valueUnit: "ms" + valueScale: 1 + valueNumDigits: "4" + plots: [ + { + object: Render.getConfig("OpaqueRangeTimer"), + prop: "gpuTime", + label: "Opaque", + color: "#FFFFFF" + }, + { + object: Render.getConfig("LinearDepth"), + prop: "gpuTime", + label: "LinearDepth", + color: "#00FF00" + },{ + object: Render.getConfig("SurfaceGeometry"), + prop: "gpuTime", + label: "SurfaceGeometry", + color: "#00FFFF" + }, + { + object: Render.getConfig("RenderDeferred"), + prop: "gpuTime", + label: "DeferredLighting", + color: "#FF00FF" + } + , + { + object: Render.getConfig("ToneAndPostRangeTimer"), + prop: "gpuTime", + label: "tone and post", + color: "#FF0000" + } + ] + } + } + +} + diff --git a/scripts/developer/utilities/render/subsurfaceScattering.qml b/scripts/developer/utilities/render/subsurfaceScattering.qml new file mode 100644 index 0000000000..47b960c98b --- /dev/null +++ b/scripts/developer/utilities/render/subsurfaceScattering.qml @@ -0,0 +1,81 @@ +// +// subsurfaceScattering.qml +// +// Created by Sam Gateau on 6/6/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "configSlider" + +Column { + spacing: 8 + Column { + id: scattering + spacing: 10 + + Column{ + CheckBox { + text: "Scattering" + checked: Render.getConfig("Scattering").enableScattering + onCheckedChanged: { Render.getConfig("Scattering").enableScattering = checked } + } + + CheckBox { + text: "Show Scattering BRDF" + checked: Render.getConfig("Scattering").showScatteringBRDF + onCheckedChanged: { Render.getConfig("Scattering").showScatteringBRDF = checked } + } + CheckBox { + text: "Show Curvature" + checked: Render.getConfig("Scattering").showCurvature + onCheckedChanged: { Render.getConfig("Scattering").showCurvature = checked } + } + CheckBox { + text: "Show Diffused Normal" + checked: Render.getConfig("Scattering").showDiffusedNormal + onCheckedChanged: { Render.getConfig("Scattering").showDiffusedNormal = checked } + } + Repeater { + model: [ "Scattering Bent Red:Scattering:bentRed:2.0", + "Scattering Bent Green:Scattering:bentGreen:2.0", + "Scattering Bent Blue:Scattering:bentBlue:2.0", + "Scattering Bent Scale:Scattering:bentScale:5.0", + "Scattering Curvature Offset:Scattering:curvatureOffset:1.0", + "Scattering Curvature Scale:Scattering:curvatureScale:2.0", + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: Render.getConfig(modelData.split(":")[1]) + property: modelData.split(":")[2] + max: modelData.split(":")[3] + min: 0.0 + } + } + CheckBox { + text: "Scattering Profile" + checked: Render.getConfig("DebugScattering").showProfile + onCheckedChanged: { Render.getConfig("DebugScattering").showProfile = checked } + } + CheckBox { + text: "Scattering Table" + checked: Render.getConfig("DebugScattering").showLUT + onCheckedChanged: { Render.getConfig("DebugScattering").showLUT = checked } + } + CheckBox { + text: "Cursor Pixel" + checked: Render.getConfig("DebugScattering").showCursorPixel + onCheckedChanged: { Render.getConfig("DebugScattering").showCursorPixel = checked } + } + CheckBox { + text: "Skin Specular Beckmann" + checked: Render.getConfig("DebugScattering").showSpecularTable + onCheckedChanged: { Render.getConfig("DebugScattering").showSpecularTable = checked } + } + } + } +} diff --git a/scripts/developer/utilities/render/surfaceGeometryPass.qml b/scripts/developer/utilities/render/surfaceGeometryPass.qml new file mode 100644 index 0000000000..1ff0efa15d --- /dev/null +++ b/scripts/developer/utilities/render/surfaceGeometryPass.qml @@ -0,0 +1,64 @@ +// +// surfaceGeometryPass.qml +// +// Created by Sam Gateau on 6/6/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "configSlider" + +Column { + spacing: 8 + Column { + id: surfaceGeometry + spacing: 10 + + Column{ + ConfigSlider { + label: qsTr("Depth Threshold [cm]") + integral: false + config: Render.getConfig("SurfaceGeometry") + property: "depthThreshold" + max: 5.0 + min: 0.0 + } + Repeater { + model: [ + "Basis Scale:basisScale:2.0:false", + "Curvature Scale:curvatureScale:100.0:false", + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: (modelData.split(":")[3] == 'true') + config: Render.getConfig("SurfaceGeometry") + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: 0.0 + } + } + CheckBox { + text: "Half Resolution" + checked: Render.getConfig("SurfaceGeometry")["resolutionLevel"] + onCheckedChanged: { Render.getConfig("SurfaceGeometry")["resolutionLevel"] = checked } + } + + Repeater { + model: [ "Diffusion Scale:SurfaceGeometry:diffuseFilterScale:2.0", + "Diffusion Depth Threshold:SurfaceGeometry:diffuseDepthThreshold:1.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: Render.getConfig(modelData.split(":")[1]) + property: modelData.split(":")[2] + max: modelData.split(":")[3] + min: 0.0 + } + } + } + } +} diff --git a/scripts/developer/utilities/tools/overlayFinder.js b/scripts/developer/utilities/tools/overlayFinder.js new file mode 100644 index 0000000000..75b8aeacfa --- /dev/null +++ b/scripts/developer/utilities/tools/overlayFinder.js @@ -0,0 +1,17 @@ +function mousePressEvent(event) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + // var pickRay = Camera.computePickRay(event.x, event.y); + // var intersection = Overlays.findRayIntersection(pickRay); + // print('intersection is: ' + intersection) +}; + +Controller.mousePressEvent.connect(function(event) { + mousePressEvent(event) +}); + +Script.scriptEnding.connect(function() { + Controller.mousePressEvent.disconnect(mousePressEvent); +}) \ No newline at end of file diff --git a/scripts/system/assets/images/ignore-target-01.svg b/scripts/system/assets/images/ignore-target-01.svg new file mode 100644 index 0000000000..98cee89ca1 --- /dev/null +++ b/scripts/system/assets/images/ignore-target-01.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/scripts/system/assets/images/lock.svg b/scripts/system/assets/images/lock.svg new file mode 100644 index 0000000000..bb9658de00 --- /dev/null +++ b/scripts/system/assets/images/lock.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/system/assets/images/tools/directory-01.svg b/scripts/system/assets/images/tools/directory-01.svg deleted file mode 100644 index d038611d69..0000000000 --- a/scripts/system/assets/images/tools/directory-01.svg +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/system/assets/images/tools/directory.svg b/scripts/system/assets/images/tools/directory.svg new file mode 100644 index 0000000000..09f3c14bf0 --- /dev/null +++ b/scripts/system/assets/images/tools/directory.svg @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/assets/images/tools/edit-01.svg b/scripts/system/assets/images/tools/edit-01.svg deleted file mode 100644 index c661c6f678..0000000000 --- a/scripts/system/assets/images/tools/edit-01.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/system/assets/images/tools/edit.svg b/scripts/system/assets/images/tools/edit.svg new file mode 100644 index 0000000000..f65e0cd84d --- /dev/null +++ b/scripts/system/assets/images/tools/edit.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/assets/images/tools/examples-01.svg b/scripts/system/assets/images/tools/examples-01.svg deleted file mode 100644 index ec4758dcde..0000000000 --- a/scripts/system/assets/images/tools/examples-01.svg +++ /dev/null @@ -1,306 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/system/assets/images/tools/ignore.svg b/scripts/system/assets/images/tools/ignore.svg new file mode 100644 index 0000000000..f315c5f249 --- /dev/null +++ b/scripts/system/assets/images/tools/ignore.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/assets/images/tools/market.svg b/scripts/system/assets/images/tools/market.svg new file mode 100644 index 0000000000..0cec030933 --- /dev/null +++ b/scripts/system/assets/images/tools/market.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/assets/images/tools/mic.svg b/scripts/system/assets/images/tools/mic.svg new file mode 100644 index 0000000000..0424f3eb49 --- /dev/null +++ b/scripts/system/assets/images/tools/mic.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/assets/images/tools/switch.svg b/scripts/system/assets/images/tools/switch.svg new file mode 100644 index 0000000000..e67a9aac04 --- /dev/null +++ b/scripts/system/assets/images/tools/switch.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/assets/images/unlock.svg b/scripts/system/assets/images/unlock.svg new file mode 100644 index 0000000000..789a8b0ed5 --- /dev/null +++ b/scripts/system/assets/images/unlock.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/system/assets/models/teleport.fbx b/scripts/system/assets/models/teleport.fbx new file mode 100644 index 0000000000..831f152add Binary files /dev/null and b/scripts/system/assets/models/teleport.fbx differ diff --git a/scripts/system/attachedEntitiesManager.js b/scripts/system/attachedEntitiesManager.js index 9ddb040297..ee852d8d65 100644 --- a/scripts/system/attachedEntitiesManager.js +++ b/scripts/system/attachedEntitiesManager.js @@ -27,37 +27,23 @@ var SHOW_TOOL_BAR = false; // tool bar if (SHOW_TOOL_BAR) { - var BUTTON_SIZE = 32; - var PADDING = 3; + var BUTTON_SIZE = 64; + var PADDING = 6; Script.include(["libraries/toolBars.js"]); - var toolBar = new ToolBar(0, 0, ToolBar.VERTICAL, "highfidelity.attachedEntities.toolbar", function(screenSize) { - return { - x: (BUTTON_SIZE + PADDING), - y: (screenSize.y / 2 - BUTTON_SIZE * 2 + PADDING) - }; - }); - var saveButton = toolBar.addOverlay("image", { + + var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.attachedEntities.toolbar"); + var lockButton = toolBar.addTool({ width: BUTTON_SIZE, height: BUTTON_SIZE, - imageURL: ".../save.png", + imageURL: Script.resolvePath("assets/images/lock.svg"), color: { red: 255, green: 255, blue: 255 }, - alpha: 1 - }); - var loadButton = toolBar.addOverlay("image", { - width: BUTTON_SIZE, - height: BUTTON_SIZE, - imageURL: ".../load.png", - color: { - red: 255, - green: 255, - blue: 255 - }, - alpha: 1 - }); + alpha: 1, + visible: true + }, false); } @@ -67,10 +53,8 @@ function mousePressEvent(event) { y: event.y }); - if (clickedOverlay == saveButton) { - manager.saveAttachedEntities(); - } else if (clickedOverlay == loadButton) { - manager.loadAttachedEntities(); + if (lockButton === toolBar.clicked(clickedOverlay)) { + manager.toggleLocked(); } } @@ -87,11 +71,12 @@ Script.scriptEnding.connect(scriptEnding); - // attached entites function AttachedEntitiesManager() { + var clothingLocked = false; + this.subscribeToMessages = function() { Messages.subscribe('Hifi-Object-Manipulation'); Messages.messageReceived.connect(this.handleWearableMessages); @@ -101,10 +86,6 @@ function AttachedEntitiesManager() { if (channel !== 'Hifi-Object-Manipulation') { return; } - // if (sender !== MyAvatar.sessionUUID) { - // print('wearablesManager got message from wrong sender'); - // return; - // } var parsedMessage = null; @@ -120,7 +101,7 @@ function AttachedEntitiesManager() { // ignore } else if (parsedMessage.action === 'release') { manager.handleEntityRelease(parsedMessage.grabbedEntity, parsedMessage.joint) - // manager.saveAttachedEntities(); + // manager.saveAttachedEntities(); } else if (parsedMessage.action === 'equip') { // manager.saveAttachedEntities(); } else { @@ -128,26 +109,9 @@ function AttachedEntitiesManager() { } } - this.avatarIsInDressingRoom = function() { - // return true if MyAvatar is near the dressing room - var possibleDressingRoom = Entities.findEntities(MyAvatar.position, DRESSING_ROOM_DISTANCE); - for (i = 0; i < possibleDressingRoom.length; i++) { - var entityID = possibleDressingRoom[i]; - var props = Entities.getEntityProperties(entityID); - if (props.name == 'Hifi-Dressing-Room-Base') { - return true; - } - } - return false; - } - this.handleEntityRelease = function(grabbedEntity, releasedFromJoint) { // if this is still equipped, just rewrite the position information. var grabData = getEntityCustomData('grabKey', grabbedEntity, {}); - if ("refCount" in grabData && grabData.refCount > 0) { - manager.updateRelativeOffsets(grabbedEntity); - return; - } var allowedJoints = getEntityCustomData('wearable', grabbedEntity, DEFAULT_WEARABLE_DATA).joints; @@ -179,29 +143,56 @@ function AttachedEntitiesManager() { } if (bestJointIndex != -1) { - var wearProps = { - parentID: MyAvatar.sessionUUID, - parentJointIndex: bestJointIndex - }; - + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = MyAvatar.sessionUUID; + wearProps.parentJointIndex = bestJointIndex; + delete wearProps.localPosition; + delete wearProps.localRotation; + var updatePresets = false; if (bestJointOffset && bestJointOffset.constructor === Array) { - if (this.avatarIsInDressingRoom() || bestJointOffset.length < 2) { - this.updateRelativeOffsets(grabbedEntity); + if (!clothingLocked || bestJointOffset.length < 2) { + // we're unlocked or this thing didn't have a preset position, so update it + updatePresets = true; } else { - // don't snap the entity to the preferred position if the avatar is in the dressing room. + // don't snap the entity to the preferred position if unlocked wearProps.localPosition = bestJointOffset[0]; wearProps.localRotation = bestJointOffset[1]; } } - Entities.editEntity(grabbedEntity, wearProps); + + Entities.deleteEntity(grabbedEntity); + //the true boolean here after add entity adds it as an 'avatar entity', which can travel with you from server to server. + + var newEntity = Entities.addEntity(wearProps, true); + + if (updatePresets) { + this.updateRelativeOffsets(newEntity); + } } else if (props.parentID != NULL_UUID) { // drop the entity and set it to have no parent (not on the avatar), unless it's being equipped in a hand. if (props.parentID === MyAvatar.sessionUUID && (props.parentJointIndex == MyAvatar.getJointIndex("RightHand") || - props.parentJointIndex == MyAvatar.getJointIndex("LeftHand"))) { + props.parentJointIndex == MyAvatar.getJointIndex("LeftHand"))) { // this is equipped on a hand -- don't clear the parent. } else { - Entities.editEntity(grabbedEntity, { parentID: NULL_UUID }); + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = NULL_UUID; + wearProps.parentJointIndex = -1; + delete wearProps.id; + delete wearProps.created; + delete wearProps.age; + delete wearProps.ageAsText; + delete wearProps.naturalDimensions; + delete wearProps.naturalPosition; + delete wearProps.actionData; + delete wearProps.sittingPoints; + delete wearProps.boundingBox; + delete wearProps.clientOnly; + delete wearProps.owningAvatarID; + delete wearProps.localPosition; + delete wearProps.localRotation; + Entities.deleteEntity(grabbedEntity); + Entities.addEntity(wearProps); } } } @@ -221,75 +212,75 @@ function AttachedEntitiesManager() { return false; } - this.saveAttachedEntities = function() { - print("--- saving attached entities ---"); - saveData = []; - var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE); - for (i = 0; i < nearbyEntities.length; i++) { - var entityID = nearbyEntities[i]; - if (this.updateRelativeOffsets(entityID)) { - var props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them - this.scrubProperties(props); - saveData.push(props); - } - } - Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData)); - } + // this.saveAttachedEntities = function() { + // print("--- saving attached entities ---"); + // saveData = []; + // var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE); + // for (i = 0; i < nearbyEntities.length; i++) { + // var entityID = nearbyEntities[i]; + // if (this.updateRelativeOffsets(entityID)) { + // var props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them + // this.scrubProperties(props); + // saveData.push(props); + // } + // } + // Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData)); + // } - this.scrubProperties = function(props) { - var toScrub = ["queryAACube", "position", "rotation", - "created", "ageAsText", "naturalDimensions", - "naturalPosition", "velocity", "acceleration", - "angularVelocity", "boundingBox"]; - toScrub.forEach(function(propertyName) { - delete props[propertyName]; - }); - // if the userData has a grabKey, clear old state - if ("userData" in props) { - try { - parsedUserData = JSON.parse(props.userData); - if ("grabKey" in parsedUserData) { - parsedUserData.grabKey.refCount = 0; - delete parsedUserData.grabKey["avatarId"]; - props["userData"] = JSON.stringify(parsedUserData); - } - } catch (e) { - } - } - } + // this.scrubProperties = function(props) { + // var toScrub = ["queryAACube", "position", "rotation", + // "created", "ageAsText", "naturalDimensions", + // "naturalPosition", "velocity", "acceleration", + // "angularVelocity", "boundingBox"]; + // toScrub.forEach(function(propertyName) { + // delete props[propertyName]; + // }); + // // if the userData has a grabKey, clear old state + // if ("userData" in props) { + // try { + // parsedUserData = JSON.parse(props.userData); + // if ("grabKey" in parsedUserData) { + // parsedUserData.grabKey.refCount = 0; + // delete parsedUserData.grabKey["avatarId"]; + // props["userData"] = JSON.stringify(parsedUserData); + // } + // } catch (e) { + // } + // } + // } - this.loadAttachedEntities = function(grabbedEntity) { - print("--- loading attached entities ---"); - jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY); - var loadData = []; - try { - loadData = JSON.parse(jsonAttachmentData); - } catch (e) { - print('error parsing saved attachment data'); - return; - } + // this.loadAttachedEntities = function(grabbedEntity) { + // print("--- loading attached entities ---"); + // jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY); + // var loadData = []; + // try { + // loadData = JSON.parse(jsonAttachmentData); + // } catch (e) { + // print('error parsing saved attachment data'); + // return; + // } - for (i = 0; i < loadData.length; i ++) { - var savedProps = loadData[ i ]; - var currentProps = Entities.getEntityProperties(savedProps.id); - if (currentProps.id == savedProps.id && - // TODO -- also check that parentJointIndex matches? - currentProps.parentID == MyAvatar.sessionUUID) { - // entity is already in-world. TODO -- patch it up? - continue; - } - this.scrubProperties(savedProps); - delete savedProps["id"]; - savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions - var loadedEntityID = Entities.addEntity(savedProps); + // for (i = 0; i < loadData.length; i ++) { + // var savedProps = loadData[ i ]; + // var currentProps = Entities.getEntityProperties(savedProps.id); + // if (currentProps.id == savedProps.id && + // // TODO -- also check that parentJointIndex matches? + // currentProps.parentID == MyAvatar.sessionUUID) { + // // entity is already in-world. TODO -- patch it up? + // continue; + // } + // this.scrubProperties(savedProps); + // delete savedProps["id"]; + // savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions + // var loadedEntityID = Entities.addEntity(savedProps, true); - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'loaded', - grabbedEntity: loadedEntityID - })); - } - } + // Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + // action: 'loaded', + // grabbedEntity: loadedEntityID + // })); + // } + // } } var manager = new AttachedEntitiesManager(); -manager.subscribeToMessages(); +manager.subscribeToMessages(); \ No newline at end of file diff --git a/scripts/system/away.js b/scripts/system/away.js index 2b2ea8a42b..25c330738f 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -34,6 +34,7 @@ var OVERLAY_DATA_HMD = { color: {red: 255, green: 255, blue: 255}, alpha: 1, scale: 2, + emissive: true, isFacingAvatar: true, drawInFront: true }; @@ -158,6 +159,8 @@ function goAway() { return; } + UserActivityLogger.toggledAway(true); + isAway = true; print('going "away"'); wasMuted = AudioDevice.getMuted(); @@ -176,9 +179,11 @@ function goAway() { // tell the Reticle, we want to stop capturing the mouse until we come back Reticle.allowMouseCapture = false; - if (HMD.active) { - Reticle.visible = false; - } + // Allow users to find their way to other applications, our menus, etc. + // For desktop, that means we want the reticle visible. + // For HMD, the hmd preview will show the system mouse because of allowMouseCapture, + // but we want to turn off our Reticle so that we don't get two in preview and a stuck one in headset. + Reticle.visible = !HMD.active; wasHmdMounted = safeGetHMDMounted(); // always remember the correct state avatarPosition = MyAvatar.position; @@ -189,6 +194,9 @@ function goActive() { if (!isAway) { return; } + + UserActivityLogger.toggledAway(false); + isAway = false; print('going "active"'); if (!wasMuted) { @@ -265,9 +273,11 @@ eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(goActive); eventMapping.from(Controller.Standard.LT).peek().to(goActive); eventMapping.from(Controller.Standard.LB).peek().to(goActive); eventMapping.from(Controller.Standard.LS).peek().to(goActive); +eventMapping.from(Controller.Standard.LeftGrip).peek().to(goActive); eventMapping.from(Controller.Standard.RT).peek().to(goActive); eventMapping.from(Controller.Standard.RB).peek().to(goActive); eventMapping.from(Controller.Standard.RS).peek().to(goActive); +eventMapping.from(Controller.Standard.RightGrip).peek().to(goActive); eventMapping.from(Controller.Standard.Back).peek().to(goActive); eventMapping.from(Controller.Standard.Start).peek().to(goActive); Controller.enableMapping(eventMappingName); diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index a7e08451bc..ef39e95880 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -316,7 +316,12 @@ Grabber.prototype.pressEvent = function(event) { return; } - if (event.isLeftButton!==true ||event.isRightButton===true || event.isMiddleButton===true) { + if (event.isLeftButton !== true || event.isRightButton === true || event.isMiddleButton === true) { + return; + } + + if (Overlays.getOverlayAtPoint(Reticle.position) > 0) { + // the mouse is pointing at an overlay; don't look for entities underneath the overlay. return; } diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ed4ac219c0..5b78f068c8 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -10,10 +10,10 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ +/* global setEntityCustomData, getEntityCustomData, vec3toStr, flatten, Xform */ Script.include("/~/system/libraries/utils.js"); - +Script.include("/~/system/libraries/Xform.js"); // // add lines where the hand ray picking is happening @@ -26,10 +26,13 @@ var WANT_DEBUG_SEARCH_NAME = null; // these tune time-averaging and "on" value for analog trigger // +var SPARK_MODEL_SCALE_FACTOR = 0.75; + var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing -var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab -var TRIGGER_GRAB_VALUE = 0.75; // Squeezed far enough to complete distant grab -var TRIGGER_OFF_VALUE = 0.15; +var TRIGGER_OFF_VALUE = 0.1; +var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab + +var COLLIDE_WITH_AV_AFTER_RELEASE_DELAY = 0.25; // seconds var BUMPER_ON_VALUE = 0.5; @@ -39,6 +42,10 @@ var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only var PICK_WITH_HAND_RAY = true; +var EQUIP_SPHERE_COLOR = { red: 179, green: 120, blue: 211 }; +var EQUIP_SPHERE_ALPHA = 0.15; +var EQUIP_SPHERE_SCALE_FACTOR = 0.65; + // // distant manipulation // @@ -47,9 +54,7 @@ var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified -var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did var MOVE_WITH_HEAD = true; // experimental head-control of distantly held objects -var FAR_TO_NEAR_GRAB_PADDING_FACTOR = 1.2; var NO_INTERSECT_COLOR = { red: 10, @@ -69,29 +74,30 @@ var LINE_ENTITY_DIMENSIONS = { var LINE_LENGTH = 500; var PICK_MAX_DISTANCE = 500; // max length of pick-ray + // // near grabbing // -var GRAB_RADIUS = 0.06; // if the ray misses but an object is this close, it will still be selected +var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. +var EQUIP_HOTSPOT_RENDER_RADIUS = 0.3; // radius used for palm vs equip-hotspot for rendering hot-spots + var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position -var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected + +var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing. +var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand + +var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. + var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed -var SHOW_GRAB_SPHERE = false; // draw a green sphere to show the grab search position and size var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds -// -// equip -// - -var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; -var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position - // // other constants // +var HOTSPOT_DRAW_DISTANCE = 10; var RIGHT_HAND = 1; var LEFT_HAND = 0; @@ -133,13 +139,11 @@ var DEFAULT_GRABBABLE_DATA = { disableReleaseVelocity: false }; - - // sometimes we want to exclude objects from being picked var USE_BLACKLIST = true; var blacklist = []; -//we've created various ways of visualizing looking for and moving distant objects +// we've created various ways of visualizing looking for and moving distant objects var USE_ENTITY_LINES_FOR_SEARCHING = false; var USE_OVERLAY_LINES_FOR_SEARCHING = true; @@ -147,29 +151,20 @@ var USE_ENTITY_LINES_FOR_MOVING = false; var USE_OVERLAY_LINES_FOR_MOVING = false; var USE_PARTICLE_BEAM_FOR_MOVING = true; - var USE_SPOTLIGHT = false; var USE_POINTLIGHT = false; +var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"]; +var FORBIDDEN_GRAB_TYPES = ['Unknown', 'Light', 'PolyLine', 'Zone']; + // states for the state machine var STATE_OFF = 0; var STATE_SEARCHING = 1; -var STATE_HOLD_SEARCHING = 2; -var STATE_DISTANCE_HOLDING = 3; -var STATE_CONTINUE_DISTANCE_HOLDING = 4; -var STATE_NEAR_GRABBING = 5; -var STATE_CONTINUE_NEAR_GRABBING = 6; -var STATE_NEAR_TRIGGER = 7; -var STATE_CONTINUE_NEAR_TRIGGER = 8; -var STATE_FAR_TRIGGER = 9; -var STATE_CONTINUE_FAR_TRIGGER = 10; -var STATE_RELEASE = 11; -var STATE_EQUIP = 12; -var STATE_HOLD = 13; -var STATE_CONTINUE_HOLD = 14; -var STATE_CONTINUE_EQUIP = 15; -var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 16; -var STATE_WAITING_FOR_EQUIP_THUMB_RELEASE = 17; +var STATE_DISTANCE_HOLDING = 2; +var STATE_NEAR_GRABBING = 3; +var STATE_NEAR_TRIGGER = 4; +var STATE_FAR_TRIGGER = 5; +var STATE_HOLD = 6; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar @@ -179,57 +174,75 @@ var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic"; var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC; var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; -function stateToName(state) { - switch (state) { - case STATE_OFF: - return "off"; - case STATE_SEARCHING: - return "searching"; - case STATE_HOLD_SEARCHING: - return "hold_searching"; - case STATE_DISTANCE_HOLDING: - return "distance_holding"; - case STATE_CONTINUE_DISTANCE_HOLDING: - return "continue_distance_holding"; - case STATE_NEAR_GRABBING: - return "near_grabbing"; - case STATE_CONTINUE_NEAR_GRABBING: - return "continue_near_grabbing"; - case STATE_NEAR_TRIGGER: - return "near_trigger"; - case STATE_CONTINUE_NEAR_TRIGGER: - return "continue_near_trigger"; - case STATE_FAR_TRIGGER: - return "far_trigger"; - case STATE_CONTINUE_FAR_TRIGGER: - return "continue_far_trigger"; - case STATE_RELEASE: - return "release"; - case STATE_EQUIP: - return "equip"; - case STATE_HOLD: - return "hold"; - case STATE_CONTINUE_HOLD: - return "continue_hold"; - case STATE_CONTINUE_EQUIP: - return "continue_equip"; - case STATE_WAITING_FOR_EQUIP_THUMB_RELEASE: - return "waiting_for_equip_thumb_release"; - case STATE_WAITING_FOR_RELEASE_THUMB_RELEASE: - return "waiting_for_release_thumb_release"; - } +var delayedDeactivateFunc; +var delayedDeactivateTimeout; +var delayedDeactivateEntityID; - return "unknown"; +var CONTROLLER_STATE_MACHINE = {}; + +CONTROLLER_STATE_MACHINE[STATE_OFF] = { + name: "off", + enterMethod: "offEnter", + updateMethod: "off" +}; +CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { + name: "searching", + updateMethod: "search" +}; +CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { + name: "distance_holding", + enterMethod: "distanceHoldingEnter", + updateMethod: "distanceHolding" +}; +CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { + name: "near_grabbing", + enterMethod: "nearGrabbingEnter", + updateMethod: "nearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_HOLD] = { + name: "hold", + enterMethod: "nearGrabbingEnter", + updateMethod: "nearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { + name: "trigger", + enterMethod: "nearTriggerEnter", + updateMethod: "nearTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = { + name: "far_trigger", + enterMethod: "farTriggerEnter", + updateMethod: "farTrigger" +}; + +function stateToName(state) { + return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???"; } function getTag() { return "grab-" + MyAvatar.sessionUUID; } +function colorPow(color, power) { + return { + red: Math.pow(color.red / 255.0, power) * 255, + green: Math.pow(color.green / 255.0, power) * 255, + blue: Math.pow(color.blue / 255.0, power) * 255 + }; +} + function entityHasActions(entityID) { return Entities.getActionIDs(entityID).length > 0; } +function findRayIntersection(pickRay, precise, include, exclude) { + var entities = Entities.findRayIntersection(pickRay, precise, include, exclude); + var overlays = Overlays.findRayIntersection(pickRay); + if (!overlays.intersects || (entities.intersects && (entities.distance <= overlays.distance))) { + return entities; + } + return overlays; +} function entityIsGrabbedByOther(entityID) { // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. var actionIDs = Entities.getActionIDs(entityID); @@ -249,6 +262,333 @@ function entityIsGrabbedByOther(entityID) { return false; } +function propsArePhysical(props) { + if (!props.dynamic) { + return false; + } + var isPhysical = (props.shapeType && props.shapeType != 'none'); + return isPhysical; +} + +var USE_ATTACH_POINT_SETTINGS = true; + +var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; +function getAttachPointSettings() { + try { + var str = Settings.getValue(ATTACH_POINT_SETTINGS); + if (str === "false") { + return {}; + } else { + return JSON.parse(str); + } + } catch (err) { + print("Error parsing attachPointSettings: " + err); + return {}; + } +} +function setAttachPointSettings(attachPointSettings) { + var str = JSON.stringify(attachPointSettings); + Settings.setValue(ATTACH_POINT_SETTINGS, str); +} +function getAttachPointForHotspotFromSettings(hotspot, hand) { + var attachPointSettings = getAttachPointSettings(); + var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand"; + var joints = attachPointSettings[hotspot.key]; + if (joints) { + return joints[jointName]; + } else { + return undefined; + } +} +function storeAttachPointForHotspotInSettings(hotspot, hand, offsetPosition, offsetRotation) { + var attachPointSettings = getAttachPointSettings(); + var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand"; + var joints = attachPointSettings[hotspot.key]; + if (!joints) { + joints = {}; + attachPointSettings[hotspot.key] = joints; + } + joints[jointName] = [offsetPosition, offsetRotation]; + setAttachPointSettings(attachPointSettings); +} + +function removeMyAvatarFromCollidesWith(origCollidesWith) { + var collidesWithSplit = origCollidesWith.split(","); + // remove myAvatar from the array + for (var i = collidesWithSplit.length - 1; i >= 0; i--) { + if (collidesWithSplit[i] === "myAvatar") { + collidesWithSplit.splice(i, 1); + } + } + return collidesWithSplit.join(); +} + +// If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, +// and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. +var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; +var EDIT_SETTING = "io.highfidelity.isEditting"; +function isEditing() { + var actualSettingValue = Settings.getValue(EDIT_SETTING) === "false" ? false : !!Settings.getValue(EDIT_SETTING); + return EXTERNALLY_MANAGED_2D_MINOR_MODE && actualSettingValue; +} +function isIn2DMode() { + // In this version, we make our own determination of whether we're aimed a HUD element, + // because other scripts (such as handControllerPointer) might be using some other visualization + // instead of setting Reticle.visible. + return (EXTERNALLY_MANAGED_2D_MINOR_MODE && + (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(Reticle.position))); +} +function restore2DMode() { + if (!EXTERNALLY_MANAGED_2D_MINOR_MODE) { + Reticle.setVisible(true); + } +} + +// EntityPropertiesCache is a helper class that contains a cache of entity properties. +// the hope is to prevent excess calls to Entity.getEntityProperties() +// +// usage: +// call EntityPropertiesCache.addEntities with all the entities that you are interested in. +// This will fetch their properties. Then call EntityPropertiesCache.getProps to receive an object +// containing a cache of all the properties previously fetched. +function EntityPropertiesCache() { + this.cache = {}; +} +EntityPropertiesCache.prototype.clear = function () { + this.cache = {}; +}; +EntityPropertiesCache.prototype.addEntity = function (entityID) { + var cacheEntry = this.cache[entityID]; + if (cacheEntry && cacheEntry.refCount) { + cacheEntry.refCount += 1; + } else { + this._updateCacheEntry(entityID); + } +}; +EntityPropertiesCache.prototype.addEntities = function (entities) { + var _this = this; + entities.forEach(function (entityID) { + _this.addEntity(entityID); + }); +}; +EntityPropertiesCache.prototype._updateCacheEntry = function (entityID) { + var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); + + // convert props.userData from a string to an object. + var userData = {}; + if (props.userData) { + try { + userData = JSON.parse(props.userData); + } catch (err) { + print("WARNING: malformed userData on " + entityID + ", name = " + props.name + ", error = " + err); + } + } + props.userData = userData; + props.refCount = 1; + + this.cache[entityID] = props; +}; +EntityPropertiesCache.prototype.update = function () { + // delete any cacheEntries with zero refCounts. + var entities = Object.keys(this.cache); + for (var i = 0; i < entities.length; i++) { + var props = this.cache[entities[i]]; + if (props.refCount === 0) { + delete this.cache[entities[i]]; + } else { + props.refCount = 0; + } + } +}; +EntityPropertiesCache.prototype.getProps = function (entityID) { + var obj = this.cache[entityID]; + return obj ? obj : undefined; +}; +EntityPropertiesCache.prototype.getGrabbableProps = function (entityID) { + var props = this.cache[entityID]; + if (props) { + return props.userData.grabbableKey ? props.userData.grabbableKey : DEFAULT_GRABBABLE_DATA; + } else { + return undefined; + } +}; +EntityPropertiesCache.prototype.getGrabProps = function (entityID) { + var props = this.cache[entityID]; + if (props) { + return props.userData.grabKey ? props.userData.grabKey : {}; + } else { + return undefined; + } +}; +EntityPropertiesCache.prototype.getWearableProps = function (entityID) { + var props = this.cache[entityID]; + if (props) { + return props.userData.wearable ? props.userData.wearable : {}; + } else { + return undefined; + } +}; +EntityPropertiesCache.prototype.getEquipHotspotsProps = function (entityID) { + var props = this.cache[entityID]; + if (props) { + return props.userData.equipHotspots ? props.userData.equipHotspots : {}; + } else { + return undefined; + } +}; + +// global cache +var entityPropertiesCache = new EntityPropertiesCache(); + +// Each overlayInfoSet describes a single equip hotspot. +// It is an object with the following keys: +// timestamp - last time this object was updated, used to delete stale hotspot overlays. +// entityID - entity assosicated with this hotspot +// localPosition - position relative to the entity +// hotspot - hotspot object +// overlays - array of overlay objects created by Overlay.addOverlay() +// currentSize - current animated scale value +// targetSize - the target of our scale animations +// type - "sphere" or "model". +function EquipHotspotBuddy() { + // holds map from {string} hotspot.key to {object} overlayInfoSet. + this.map = {}; + + // array of all hotspots that are highlighed. + this.highlightedHotspots = []; +} +EquipHotspotBuddy.prototype.clear = function () { + var keys = Object.keys(this.map); + for (var i = 0; i < keys.length; i++) { + var overlayInfoSet = this.map[keys[i]]; + this.deleteOverlayInfoSet(overlayInfoSet); + } + this.map = {}; + this.highlightedHotspots = []; +}; +EquipHotspotBuddy.prototype.highlightHotspot = function (hotspot) { + this.highlightedHotspots.push(hotspot.key); +}; +EquipHotspotBuddy.prototype.updateHotspot = function (hotspot, timestamp) { + var overlayInfoSet = this.map[hotspot.key]; + if (!overlayInfoSet) { + // create a new overlayInfoSet + overlayInfoSet = { + timestamp: timestamp, + entityID: hotspot.entityID, + localPosition: hotspot.localPosition, + hotspot: hotspot, + currentSize: 0, + targetSize: 1, + overlays: [] + }; + + var diameter = hotspot.radius * 2; + + if (hotspot.modelURL) { + // override default sphere with a user specified model + overlayInfoSet.overlays.push(Overlays.addOverlay("model", { + url: hotspot.modelURL, + position: hotspot.worldPosition, + rotation: {x: 0, y: 0, z: 0, w: 1}, + dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR, + scale: hotspot.modelScale, + ignoreRayIntersection: true + })); + overlayInfoSet.type = "model"; + } else { + // default sphere overlay + overlayInfoSet.overlays.push(Overlays.addOverlay("sphere", { + position: hotspot.worldPosition, + rotation: {x: 0, y: 0, z: 0, w: 1}, + dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR, + color: EQUIP_SPHERE_COLOR, + alpha: EQUIP_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + })); + overlayInfoSet.type = "sphere"; + } + + this.map[hotspot.key] = overlayInfoSet; + } else { + overlayInfoSet.timestamp = timestamp; + } +}; +EquipHotspotBuddy.prototype.updateHotspots = function (hotspots, timestamp) { + var _this = this; + hotspots.forEach(function (hotspot) { + _this.updateHotspot(hotspot, timestamp); + }); + this.highlightedHotspots = []; +}; +EquipHotspotBuddy.prototype.update = function (deltaTime, timestamp) { + + var HIGHLIGHT_SIZE = 1.1; + var NORMAL_SIZE = 1.0; + + var keys = Object.keys(this.map); + for (var i = 0; i < keys.length; i++) { + var overlayInfoSet = this.map[keys[i]]; + + // this overlayInfo is highlighted. + if (this.highlightedHotspots.indexOf(keys[i]) != -1) { + overlayInfoSet.targetSize = HIGHLIGHT_SIZE; + } else { + overlayInfoSet.targetSize = NORMAL_SIZE; + } + + // start to fade out this hotspot. + if (overlayInfoSet.timestamp != timestamp) { + // because this item timestamp has expired, it might not be in the cache anymore.... + entityPropertiesCache.addEntity(overlayInfoSet.entityID); + overlayInfoSet.targetSize = 0; + } + + // animate the size. + var SIZE_TIMESCALE = 0.1; + var tau = deltaTime / SIZE_TIMESCALE; + if (tau > 1.0) { + tau = 1.0; + } + overlayInfoSet.currentSize += (overlayInfoSet.targetSize - overlayInfoSet.currentSize) * tau; + + if (overlayInfoSet.timestamp != timestamp && overlayInfoSet.currentSize <= 0.05) { + // this is an old overlay, that has finished fading out, delete it! + overlayInfoSet.overlays.forEach(function (overlay) { + Overlays.deleteOverlay(overlay); + }); + delete this.map[keys[i]]; + } else { + // update overlay position, rotation to follow the object it's attached to. + + var props = entityPropertiesCache.getProps(overlayInfoSet.entityID); + var entityXform = new Xform(props.rotation, props.position); + var position = entityXform.xformPoint(overlayInfoSet.localPosition); + + var dimensions; + if (overlayInfoSet.type == "sphere") { + dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize * EQUIP_SPHERE_SCALE_FACTOR; + } else { + dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize; + } + + overlayInfoSet.overlays.forEach(function (overlay) { + Overlays.editOverlay(overlay, { + position: position, + rotation: props.rotation, + dimensions: dimensions + }); + }); + } + } +}; + +// global EquipHotspotBuddy instance +var equipHotspotBuddy = new EquipHotspotBuddy(); + function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -258,10 +598,10 @@ function MyController(hand) { this.getHandPosition = MyAvatar.getLeftPalmPosition; // this.getHandRotation = MyAvatar.getLeftPalmRotation; } - this.getHandRotation = function() { + this.getHandRotation = function () { var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; return Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); - } + }; this.actionID = null; // action this script created... this.grabbedEntity = null; // on this entity. @@ -270,21 +610,23 @@ function MyController(hand) { this.entityActivated = false; this.triggerValue = 0; // rolling average of trigger value + this.triggerClicked = false; this.rawTriggerValue = 0; - this.rawBumperValue = 0; + this.rawSecondaryValue = 0; this.rawThumbValue = 0; - //for visualizations + // for visualizations this.overlayLine = null; this.particleBeamObject = null; - this.grabSphere = null; - //for lights + // for lights this.spotlight = null; this.pointlight = null; this.overlayLine = null; this.searchSphere = null; + this.waitForTriggerRelease = false; + // how far from camera to search intersection? var DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; this.intersectionDistance = 0.0; @@ -297,75 +639,78 @@ function MyController(hand) { this.lastPickTime = 0; this.lastUnequipCheckTime = 0; + this.equipOverlayInfoSetMap = {}; + var _this = this; - this.update = function() { + var suppressedIn2D = [STATE_OFF, STATE_SEARCHING]; + this.ignoreInput = function () { + // We've made the decision to use 'this' for new code, even though it is fragile, + // in order to keep/ the code uniform without making any no-op line changes. + return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); + }; + + this.update = function (deltaTime, timestamp) { this.updateSmoothedTrigger(); - switch (this.state) { - case STATE_OFF: - this.off(); - break; - case STATE_SEARCHING: - case STATE_HOLD_SEARCHING: - this.search(); - break; - case STATE_DISTANCE_HOLDING: - this.distanceHolding(); - break; - case STATE_CONTINUE_DISTANCE_HOLDING: - this.continueDistanceHolding(); - break; - case STATE_NEAR_GRABBING: - case STATE_EQUIP: - case STATE_HOLD: - this.nearGrabbing(); - break; - case STATE_WAITING_FOR_EQUIP_THUMB_RELEASE: - this.waitingForEquipThumbRelease(); - break; - case STATE_WAITING_FOR_RELEASE_THUMB_RELEASE: - this.waitingForReleaseThumbRelease(); - break; - case STATE_CONTINUE_NEAR_GRABBING: - case STATE_CONTINUE_HOLD: - case STATE_CONTINUE_EQUIP: - this.continueNearGrabbing(); - break; - case STATE_NEAR_TRIGGER: - this.nearTrigger(); - break; - case STATE_CONTINUE_NEAR_TRIGGER: - this.continueNearTrigger(); - break; - case STATE_FAR_TRIGGER: - this.farTrigger(); - break; - case STATE_CONTINUE_FAR_TRIGGER: - this.continueFarTrigger(); - break; - case STATE_RELEASE: - this.release(); - break; + if (this.ignoreInput()) { + this.turnOffVisualizations(); + return; + } + + if (CONTROLLER_STATE_MACHINE[this.state]) { + var updateMethodName = CONTROLLER_STATE_MACHINE[this.state].updateMethod; + var updateMethod = this[updateMethodName]; + if (updateMethod) { + updateMethod.call(this, deltaTime, timestamp); + } else { + print("WARNING: could not find updateMethod for state " + stateToName(this.state)); + } + } else { + print("WARNING: could not find state " + this.state + " in state machine"); } }; - this.callEntityMethodOnGrabbed = function(entityMethodName) { + this.callEntityMethodOnGrabbed = function (entityMethodName) { var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args); - } - - this.setState = function(newState) { - this.grabSphereOff(); - if (WANT_DEBUG || WANT_DEBUG_STATE) { - print("STATE (" + this.hand + "): " + stateToName(this.state) + " --> " + - stateToName(newState) + ", hand: " + this.hand); - } - this.state = newState; }; - this.debugLine = function(closePoint, farPoint, color) { + this.setState = function (newState, reason) { + + if (WANT_DEBUG || WANT_DEBUG_STATE) { + var oldStateName = stateToName(this.state); + var newStateName = stateToName(newState); + print("STATE (" + this.hand + "): " + newStateName + " <-- " + oldStateName + ", reason = " + reason); + } + + // exit the old state + if (CONTROLLER_STATE_MACHINE[this.state]) { + var exitMethodName = CONTROLLER_STATE_MACHINE[this.state].exitMethod; + var exitMethod = this[exitMethodName]; + if (exitMethod) { + exitMethod.call(this); + } + } else { + print("WARNING: could not find state " + this.state + " in state machine"); + } + + this.state = newState; + + // enter the new state + if (CONTROLLER_STATE_MACHINE[newState]) { + var enterMethodName = CONTROLLER_STATE_MACHINE[newState].enterMethod; + var enterMethod = this[enterMethodName]; + if (enterMethod) { + enterMethod.call(this); + } + } else { + print("WARNING: could not find newState " + newState + " in state machine"); + } + }; + + this.debugLine = function (closePoint, farPoint, color) { Entities.addEntity({ type: "Line", name: "Grab Debug Entity", @@ -385,7 +730,7 @@ function MyController(hand) { }); }; - this.lineOn = function(closePoint, farPoint, color) { + this.lineOn = function (closePoint, farPoint, color) { // draw a line if (this.pointer === null) { this.pointer = Entities.addEntity({ @@ -416,86 +761,68 @@ function MyController(hand) { } }; - var SEARCH_SPHERE_ALPHA = 0.5; - this.searchSphereOn = function(location, size, color) { + this.searchSphereOn = function (location, size, color) { + + var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); + var brightColor = colorPow(color, 0.06); if (this.searchSphere === null) { var sphereProperties = { position: location, - size: size, - color: color, - alpha: SEARCH_SPHERE_ALPHA, + rotation: rotation, + outerRadius: size * 1.2, + innerColor: brightColor, + outerColor: color, + innerAlpha: 0.9, + outerAlpha: 0.0, solid: true, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. visible: true - } - this.searchSphere = Overlays.addOverlay("sphere", sphereProperties); + }; + this.searchSphere = Overlays.addOverlay("circle3d", sphereProperties); } else { Overlays.editOverlay(this.searchSphere, { position: location, - size: size, - color: color, + rotation: rotation, + innerColor: brightColor, + outerColor: color, + innerAlpha: 1.0, + outerAlpha: 0.0, + outerRadius: size * 1.2, visible: true }); } - } - - this.grabSphereOn = function() { - var color = {red: 0, green: 255, blue: 0}; - if (this.grabSphere === null) { - var sphereProperties = { - position: this.getHandPosition(), - size: GRAB_RADIUS*2, - color: color, - alpha: 0.1, - solid: true, - visible: true - } - this.grabSphere = Overlays.addOverlay("sphere", sphereProperties); - } else { - Overlays.editOverlay(this.grabSphere, { - position: this.getHandPosition(), - size: GRAB_RADIUS*2, - color: color, - alpha: 0.1, - solid: true, - visible: true - }); - } - } - - this.grabSphereOff = function() { - if (this.grabSphere !== null) { - Overlays.deleteOverlay(this.grabSphere); - this.grabSphere = null; - } }; - this.overlayLineOn = function(closePoint, farPoint, color) { + this.overlayLineOn = function (closePoint, farPoint, color) { if (this.overlayLine === null) { var lineProperties = { - lineWidth: 5, + glow: 1.0, start: closePoint, end: farPoint, color: color, ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. visible: true, alpha: 1 }; this.overlayLine = Overlays.addOverlay("line3d", lineProperties); } else { - var success = Overlays.editOverlay(this.overlayLine, { + Overlays.editOverlay(this.overlayLine, { lineWidth: 5, start: closePoint, end: farPoint, color: color, visible: true, ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. alpha: 1 }); } }; - this.searchIndicatorOn = function(distantPickRay) { + this.searchIndicatorOn = function (distantPickRay) { var handPosition = distantPickRay.origin; var SEARCH_SPHERE_SIZE = 0.011; var SEARCH_SPHERE_FOLLOW_RATE = 0.50; @@ -509,14 +836,14 @@ function MyController(hand) { var searchSphereLocation = Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, this.searchSphereDistance)); this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance, - (this.triggerSmoothedGrab() || this.bumperSqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR); + (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR); if ((USE_OVERLAY_LINES_FOR_SEARCHING === true) && PICK_WITH_HAND_RAY) { this.overlayLineOn(handPosition, searchSphereLocation, - (this.triggerSmoothedGrab() || this.bumperSqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR); + (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR); } - } + }; - this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { + this.handleDistantParticleBeam = function (handPosition, objectPosition, color) { var handToObject = Vec3.subtract(objectPosition, handPosition); var finalRotationObject = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); @@ -532,7 +859,7 @@ function MyController(hand) { } }; - this.createParticleBeam = function(positionObject, orientationObject, color, speed, spread, lifespan) { + this.createParticleBeam = function (positionObject, orientationObject, color, speed, spread, lifespan) { var particleBeamPropertiesObject = { type: "ParticleEffect", @@ -581,12 +908,12 @@ function MyController(hand) { "alphaFinish": 1, "additiveBlending": 0, "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" - } + }; this.particleBeamObject = Entities.addEntity(particleBeamPropertiesObject); }; - this.updateParticleBeam = function(positionObject, orientationObject, color, speed, spread, lifespan) { + this.updateParticleBeam = function (positionObject, orientationObject, color, speed, spread, lifespan) { Entities.editEntity(this.particleBeamObject, { rotation: orientationObject, position: positionObject, @@ -595,17 +922,10 @@ function MyController(hand) { emitSpeed: speed, speedSpread: spread, lifespan: lifespan - }) + }); }; - this.renewParticleBeamLifetime = function() { - var props = Entities.getEntityProperties(this.particleBeamObject, "age"); - Entities.editEntity(this.particleBeamObject, { - lifetime: TEMPORARY_PARTICLE_BEAM_LIFETIME + props.age // renew lifetime - }) - } - - this.evalLightWorldTransform = function(modelPos, modelRot) { + this.evalLightWorldTransform = function (modelPos, modelRot) { var MODEL_LIGHT_POSITION = { x: 0, @@ -625,7 +945,7 @@ function MyController(hand) { }; }; - this.handleSpotlight = function(parentID, position) { + this.handleSpotlight = function (parentID) { var LIFETIME = 100; var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); @@ -649,20 +969,20 @@ function MyController(hand) { exponent: 0.3, cutoff: 20, lifetime: LIFETIME, - position: lightTransform.p, + position: lightTransform.p }; if (this.spotlight === null) { this.spotlight = Entities.addEntity(lightProperties); } else { Entities.editEntity(this.spotlight, { - //without this, this light would maintain rotation with its parent - rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), - }) + // without this, this light would maintain rotation with its parent + rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0) + }); } }; - this.handlePointLight = function(parentID, position) { + this.handlePointLight = function (parentID) { var LIFETIME = 100; var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); @@ -686,31 +1006,29 @@ function MyController(hand) { exponent: 0.3, cutoff: 20, lifetime: LIFETIME, - position: lightTransform.p, + position: lightTransform.p }; if (this.pointlight === null) { this.pointlight = Entities.addEntity(lightProperties); - } else { - } }; - this.lineOff = function() { + this.lineOff = function () { if (this.pointer !== null) { Entities.deleteEntity(this.pointer); } this.pointer = null; }; - this.overlayLineOff = function() { + this.overlayLineOff = function () { if (this.overlayLine !== null) { Overlays.deleteOverlay(this.overlayLine); } this.overlayLine = null; }; - this.searchSphereOff = function() { + this.searchSphereOff = function () { if (this.searchSphere !== null) { Overlays.deleteOverlay(this.searchSphere); this.searchSphere = null; @@ -719,14 +1037,14 @@ function MyController(hand) { } }; - this.particleBeamOff = function() { + this.particleBeamOff = function () { if (this.particleBeamObject !== null) { Entities.deleteEntity(this.particleBeamObject); this.particleBeamObject = null; } - } + }; - this.turnLightsOff = function() { + this.turnLightsOff = function () { if (this.spotlight !== null) { Entities.deleteEntity(this.spotlight); this.spotlight = null; @@ -738,15 +1056,7 @@ function MyController(hand) { } }; - this.propsArePhysical = function(props) { - if (!props.dynamic) { - return false; - } - var isPhysical = (props.shapeType && props.shapeType != 'none'); - return isPhysical; - } - - this.turnOffVisualizations = function() { + this.turnOffVisualizations = function () { if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { this.lineOff(); } @@ -759,339 +1069,506 @@ function MyController(hand) { this.particleBeamOff(); } this.searchSphereOff(); - - Reticle.setVisible(true); + restore2DMode(); }; - this.triggerPress = function(value) { + this.triggerPress = function (value) { _this.rawTriggerValue = value; }; - this.bumperPress = function(value) { - _this.rawBumperValue = value; + this.triggerClick = function (value) { + _this.triggerClicked = value; }; - this.updateSmoothedTrigger = function() { + this.secondaryPress = function (value) { + _this.rawSecondaryValue = value; + }; + + this.updateSmoothedTrigger = function () { var triggerValue = this.rawTriggerValue; // smooth out trigger value this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); }; - this.triggerSmoothedGrab = function() { - return this.triggerValue > TRIGGER_GRAB_VALUE; + this.triggerSmoothedGrab = function () { + return this.triggerClicked; }; - this.triggerSmoothedSqueezed = function() { + this.triggerSmoothedSqueezed = function () { return this.triggerValue > TRIGGER_ON_VALUE; }; - this.triggerSmoothedReleased = function() { + this.triggerSmoothedReleased = function () { return this.triggerValue < TRIGGER_OFF_VALUE; }; - this.bumperSqueezed = function() { - return _this.rawBumperValue > BUMPER_ON_VALUE; + this.secondarySqueezed = function () { + return _this.rawSecondaryValue > BUMPER_ON_VALUE; }; - this.bumperReleased = function() { - return _this.rawBumperValue < BUMPER_ON_VALUE; + this.secondaryReleased = function () { + return _this.rawSecondaryValue < BUMPER_ON_VALUE; }; - // this.triggerOrBumperSqueezed = function() { - // return triggerSmoothedSqueezed() || bumperSqueezed(); + // this.triggerOrsecondarySqueezed = function () { + // return triggerSmoothedSqueezed() || secondarySqueezed(); // } - // this.triggerAndBumperReleased = function() { - // return triggerSmoothedReleased() && bumperReleased(); + // this.triggerAndSecondaryReleased = function () { + // return triggerSmoothedReleased() && secondaryReleased(); // } - this.thumbPress = function(value) { + this.thumbPress = function (value) { _this.rawThumbValue = value; - } + }; - this.thumbPressed = function() { + this.thumbPressed = function () { return _this.rawThumbValue > THUMB_ON_VALUE; }; - this.thumbReleased = function() { + this.thumbReleased = function () { return _this.rawThumbValue < THUMB_ON_VALUE; }; - this.off = function() { - if (this.triggerSmoothedSqueezed() || this.bumperSqueezed()) { + this.off = function (deltaTime, timestamp) { + if (this.triggerSmoothedReleased()) { + this.waitForTriggerRelease = false; + } + if (!this.waitForTriggerRelease && this.triggerSmoothedSqueezed()) { this.lastPickTime = 0; var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation; if (this.triggerSmoothedSqueezed()) { - this.setState(STATE_SEARCHING); - } else if (this.bumperSqueezed()) { - this.setState(STATE_HOLD_SEARCHING); + this.setState(STATE_SEARCHING, "trigger squeeze detected"); + return; } } + + var candidateEntities = Entities.findEntities(this.getHandPosition(), EQUIP_HOTSPOT_RENDER_RADIUS); + entityPropertiesCache.addEntities(candidateEntities); + var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); + if (!this.waitForTriggerRelease) { + this.updateEquipHaptics(potentialEquipHotspot); + } + + var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); + equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); + if (potentialEquipHotspot) { + equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); + } }; - this.search = function() { + this.clearEquipHaptics = function () { + this.prevPotentialEquipHotspot = null; + }; + + this.updateEquipHaptics = function (potentialEquipHotspot) { + if (potentialEquipHotspot && !this.prevPotentialEquipHotspot || + !potentialEquipHotspot && this.prevPotentialEquipHotspot) { + Controller.triggerShortHapticPulse(0.5, this.hand); + } + this.prevPotentialEquipHotspot = potentialEquipHotspot; + }; + + // Performs ray pick test from the hand controller into the world + // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND + // @returns {object} returns object with two keys entityID and distance + // + this.calcRayPickInfo = function (hand) { + + var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var pose = Controller.getPoseValue(standardControllerValue); + var worldHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); + var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); + + var pickRay = { + origin: PICK_WITH_HAND_RAY ? worldHandPosition : Camera.position, + direction: PICK_WITH_HAND_RAY ? Quat.getUp(worldHandRotation) : Vec3.mix(Quat.getUp(worldHandRotation), + Quat.getFront(Camera.orientation), + HAND_HEAD_MIX_RATIO), + length: PICK_MAX_DISTANCE + }; + + var result = { + entityID: null, + searchRay: pickRay, + distance: PICK_MAX_DISTANCE + }; + + var now = Date.now(); + if (now - this.lastPickTime < MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + return result; + } + this.lastPickTime = now; + + var directionNormalized = Vec3.normalize(pickRay.direction); + var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); + var pickRayBacked = { + origin: Vec3.subtract(pickRay.origin, directionBacked), + direction: pickRay.direction + }; + + var intersection; + if (USE_BLACKLIST === true && blacklist.length !== 0) { + intersection = findRayIntersection(pickRayBacked, true, [], blacklist); + } else { + intersection = findRayIntersection(pickRayBacked, true); + } + + if (intersection.intersects) { + return { + entityID: intersection.entityID, + overlayID: intersection.overlayID, + searchRay: pickRay, + distance: Vec3.distance(pickRay.origin, intersection.intersection) + }; + } else { + return result; + } + }; + + this.entityWantsTrigger = function (entityID) { + var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID); + return grabbableProps && grabbableProps.wantsTrigger; + }; + + // returns a list of all equip-hotspots assosiated with this entity. + // @param {UUID} entityID + // @returns {Object[]} array of objects with the following fields. + // * key {string} a string that can be used to uniquely identify this hotspot + // * entityID {UUID} + // * localPosition {Vec3} position of the hotspot in object space. + // * worldPosition {vec3} position of the hotspot in world space. + // * radius {number} radius of equip hotspot + // * joints {Object} keys are joint names values are arrays of two elements: + // offset position {Vec3} and offset rotation {Quat}, both are in the coordinate system of the joint. + // * modelURL {string} url for model to use instead of default sphere. + // * modelScale {Vec3} scale factor for model + this.collectEquipHotspots = function (entityID) { + var result = []; + var props = entityPropertiesCache.getProps(entityID); + var entityXform = new Xform(props.rotation, props.position); + var equipHotspotsProps = entityPropertiesCache.getEquipHotspotsProps(entityID); + if (equipHotspotsProps && equipHotspotsProps.length > 0) { + var i, length = equipHotspotsProps.length; + for (i = 0; i < length; i++) { + var hotspot = equipHotspotsProps[i]; + if (hotspot.position && hotspot.radius && hotspot.joints) { + result.push({ + key: entityID.toString() + i.toString(), + entityID: entityID, + localPosition: hotspot.position, + worldPosition: entityXform.xformPoint(hotspot.position), + radius: hotspot.radius, + joints: hotspot.joints, + modelURL: hotspot.modelURL, + modelScale: hotspot.modelScale + }); + } + } + } else { + var wearableProps = entityPropertiesCache.getWearableProps(entityID); + if (wearableProps && wearableProps.joints) { + result.push({ + key: entityID.toString() + "0", + entityID: entityID, + localPosition: {x: 0, y: 0, z: 0}, + worldPosition: entityXform.pos, + radius: EQUIP_RADIUS, + joints: wearableProps.joints, + modelURL: null, + modelScale: null + }); + } + } + return result; + }; + + this.hotspotIsEquippable = function (hotspot) { + var props = entityPropertiesCache.getProps(hotspot.entityID); + var grabProps = entityPropertiesCache.getGrabProps(hotspot.entityID); + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0; + var okToEquipFromOtherHand = ((this.getOtherHandController().state == STATE_NEAR_GRABBING || + this.getOtherHandController().state == STATE_DISTANCE_HOLDING) && + this.getOtherHandController().grabbedEntity == hotspot.entityID); + if (refCount > 0 && !okToEquipFromOtherHand) { + if (debug) { + print("equip is skipping '" + props.name + "': grabbed by someone else"); + } + return false; + } + + return true; + }; + + this.entityIsGrabbable = function (entityID) { + var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID); + var grabProps = entityPropertiesCache.getGrabProps(entityID); + var props = entityPropertiesCache.getProps(entityID); + var physical = propsArePhysical(props); + var grabbable = false; + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + if (physical) { + // physical things default to grabbable + grabbable = true; + } else { + // non-physical things default to non-grabbable unless they are already grabbed + if ("refCount" in grabProps && grabProps.refCount > 0) { + grabbable = true; + } else { + grabbable = false; + } + } + + if (grabbableProps.hasOwnProperty("grabbable")) { + grabbable = grabbableProps.grabbable; + } + + if (!grabbable && !grabbableProps.wantsTrigger) { + if (debug) { + print("grab is skipping '" + props.name + "': not grabbable."); + } + return false; + } + if (FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0) { + if (debug) { + print("grab is skipping '" + props.name + "': forbidden entity type."); + } + return false; + } + if (props.locked && !grabbableProps.wantsTrigger) { + if (debug) { + print("grab is skipping '" + props.name + "': locked and not triggerable."); + } + return false; + } + if (FORBIDDEN_GRAB_NAMES.indexOf(props.name) >= 0) { + if (debug) { + print("grab is skipping '" + props.name + "': forbidden name."); + } + return false; + } + + return true; + }; + + this.entityIsDistanceGrabbable = function (entityID, handPosition) { + if (!this.entityIsGrabbable(entityID)) { + return false; + } + + var props = entityPropertiesCache.getProps(entityID); + var distance = Vec3.distance(props.position, handPosition); + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + // we can't distance-grab non-physical + var isPhysical = propsArePhysical(props); + if (!isPhysical) { + if (debug) { + print("distance grab is skipping '" + props.name + "': not physical"); + } + return false; + } + + if (distance > PICK_MAX_DISTANCE) { + // too far away, don't grab + if (debug) { + print("distance grab is skipping '" + props.name + "': too far away."); + } + return false; + } + + if (entityIsGrabbedByOther(entityID)) { + // don't distance grab something that is already grabbed. + if (debug) { + print("distance grab is skipping '" + props.name + "': already grabbed by another."); + } + return false; + } + + return true; + }; + + this.entityIsNearGrabbable = function (entityID, handPosition, maxDistance) { + + if (!this.entityIsGrabbable(entityID)) { + return false; + } + + var props = entityPropertiesCache.getProps(entityID); + var distance = Vec3.distance(props.position, handPosition); + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + if (distance > maxDistance) { + // too far away, don't grab + if (debug) { + print(" grab is skipping '" + props.name + "': too far away."); + } + return false; + } + + return true; + }; + + this.chooseNearEquipHotspots = function (candidateEntities, distance) { + var equippableHotspots = flatten(candidateEntities.map(function (entityID) { + return _this.collectEquipHotspots(entityID); + })).filter(function (hotspot) { + return (_this.hotspotIsEquippable(hotspot) && + Vec3.distance(hotspot.worldPosition, _this.getHandPosition()) < hotspot.radius + distance); + }); + return equippableHotspots; + }; + + this.chooseBestEquipHotspot = function (candidateEntities) { + var DISTANCE = 0; + var equippableHotspots = this.chooseNearEquipHotspots(candidateEntities, DISTANCE); + if (equippableHotspots.length > 0) { + // sort by distance + equippableHotspots.sort(function (a, b) { + var aDistance = Vec3.distance(a.worldPosition, this.getHandPosition()); + var bDistance = Vec3.distance(b.worldPosition, this.getHandPosition()); + return aDistance - bDistance; + }); + return equippableHotspots[0]; + } else { + return null; + } + }; + + this.search = function (deltaTime, timestamp) { + var _this = this; + var name; + this.grabbedEntity = null; this.isInitialGrab = false; this.shouldResetParentOnRelease = false; this.checkForStrayChildren(); - if (this.state == STATE_SEARCHING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - return; - } - if (this.state == STATE_HOLD_SEARCHING && this.bumperReleased()) { - this.setState(STATE_RELEASE); + if (this.triggerSmoothedReleased()) { + this.setState(STATE_OFF, "trigger released"); return; } - // the trigger is being pressed, so do a ray test to see what we are hitting var handPosition = this.getHandPosition(); - if (SHOW_GRAB_SPHERE) { - this.grabSphereOn(); - } + var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); + entityPropertiesCache.addEntities(candidateEntities); - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var currentHandRotation = Controller.getPoseValue(controllerHandInput).rotation; - var currentControllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, - Controller.getPoseValue(controllerHandInput).translation), - MyAvatar.position); - var handDeltaRotation = Quat.multiply(currentHandRotation, Quat.inverse(this.startingHandRotation)); - - var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? - Controller.Standard.RightHand : Controller.Standard.LeftHand); - var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); - - var distantPickRay = { - origin: PICK_WITH_HAND_RAY ? currentControllerPosition : Camera.position, - direction: PICK_WITH_HAND_RAY ? Quat.getUp(controllerRotation) : Vec3.mix(Quat.getUp(controllerRotation), - Quat.getFront(Camera.orientation), - HAND_HEAD_MIX_RATIO), - length: PICK_MAX_DISTANCE - }; - - // Pick at some maximum rate, not always - var pickRays = []; - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - pickRays = [distantPickRay]; - this.lastPickTime = now; - } - - rayPickedCandidateEntities = []; // the list of candidates to consider grabbing - - this.intersectionDistance = 0.0; - for (var index = 0; index < pickRays.length; ++index) { - var pickRay = pickRays[index]; - var directionNormalized = Vec3.normalize(pickRay.direction); - var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); - var pickRayBacked = { - origin: Vec3.subtract(pickRay.origin, directionBacked), - direction: pickRay.direction - }; - - var intersection; - - if (USE_BLACKLIST === true && blacklist.length !== 0) { - intersection = Entities.findRayIntersection(pickRayBacked, true, [], blacklist); - } else { - intersection = Entities.findRayIntersection(pickRayBacked, true); - } - - if (intersection.intersects) { - rayPickedCandidateEntities.push(intersection.entityID); - this.intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); - } - } - - nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); - candidateEntities = rayPickedCandidateEntities.concat(nearPickedCandidateEntities); - - var forbiddenNames = ["Grab Debug Entity", "grab pointer"]; - var forbiddenTypes = ['Unknown', 'Light', 'PolyLine', 'Zone']; - - var minDistance = PICK_MAX_DISTANCE; - var i, props, distance, grabbableData; - this.grabbedEntity = null; - for (i = 0; i < candidateEntities.length; i++) { - var grabbableDataForCandidate = - getEntityCustomData(GRABBABLE_DATA_KEY, candidateEntities[i], DEFAULT_GRABBABLE_DATA); - var grabDataForCandidate = getEntityCustomData(GRAB_USER_DATA_KEY, candidateEntities[i], {}); - var propsForCandidate = Entities.getEntityProperties(candidateEntities[i], GRABBABLE_PROPERTIES); - var near = (nearPickedCandidateEntities.indexOf(candidateEntities[i]) >= 0); - - var isPhysical = this.propsArePhysical(propsForCandidate); - var grabbable; - if (isPhysical) { - // physical things default to grabbable - grabbable = true; - } else { - // non-physical things default to non-grabbable unless they are already grabbed - if ("refCount" in grabDataForCandidate && grabDataForCandidate.refCount > 0) { - grabbable = true; - } else { - grabbable = false; - } - } - - if ("grabbable" in grabbableDataForCandidate) { - // if userData indicates that this is grabbable or not, override the default. - grabbable = grabbableDataForCandidate.grabbable; - } - - if (!grabbable && !grabbableDataForCandidate.wantsTrigger) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': not grabbable."); - } - continue; - } - if (forbiddenTypes.indexOf(propsForCandidate.type) >= 0) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': forbidden entity type."); - } - continue; - } - if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': locked and not triggerable."); - } - continue; - } - if (forbiddenNames.indexOf(propsForCandidate.name) >= 0) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': forbidden name."); - } - continue; - } - - distance = Vec3.distance(propsForCandidate.position, handPosition); - if (distance > PICK_MAX_DISTANCE) { - // too far away, don't grab - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': too far away."); - } - continue; - } - if (propsForCandidate.parentID != NULL_UUID && this.state == STATE_HOLD_SEARCHING) { - // don't allow a double-equip - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': it's a child"); - } - continue; - } - - if (this.state == STATE_SEARCHING && - !isPhysical && distance > NEAR_PICK_MAX_DISTANCE && !near && !grabbableDataForCandidate.wantsTrigger) { - // we can't distance-grab non-physical - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': not physical and too far for near-grab"); - } - continue; - } - - if (distance < minDistance) { - this.grabbedEntity = candidateEntities[i]; - minDistance = distance; - props = propsForCandidate; - grabbableData = grabbableDataForCandidate; - } - } - if ((this.grabbedEntity !== null) && (this.triggerSmoothedGrab() || this.bumperSqueezed())) { - // We are squeezing enough to grab, and we've found an entity that we'll try to do something with. - var near = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE; - var isPhysical = this.propsArePhysical(props); - - // near or far trigger - if (grabbableData.wantsTrigger) { - this.setState(near ? STATE_NEAR_TRIGGER : STATE_FAR_TRIGGER); + var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); + if (potentialEquipHotspot) { + if (this.triggerSmoothedGrab()) { + this.grabbedHotspot = potentialEquipHotspot; + this.grabbedEntity = potentialEquipHotspot.entityID; + this.setState(STATE_HOLD, "eqipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'"); return; } - // near grab with action or equip - var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); - var refCount = ("refCount" in grabData) ? grabData.refCount : 0; - if (near && (refCount < 1 || entityHasActions(this.grabbedEntity))) { - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // (this.state == STATE_HOLD_SEARCHING) - // if there was already an action, we'll need to set the parent back to null once we release - this.shouldResetParentOnRelease = true; - this.previousParentID = props.parentID; - this.previousParentJointIndex = props.parentJointIndex; - this.setState(STATE_HOLD); - } - return; + } + + var grabbableEntities = candidateEntities.filter(function (entity) { + return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); + }); + + var rayPickInfo = this.calcRayPickInfo(this.hand); + if (rayPickInfo.entityID) { + this.intersectionDistance = rayPickInfo.distance; + entityPropertiesCache.addEntity(rayPickInfo.entityID); + if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { + grabbableEntities.push(rayPickInfo.entityID); } - // far grab - if (isPhysical && !near) { - if (entityIsGrabbedByOther(this.grabbedEntity)) { - // don't distance grab something that is already grabbed. - if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': already grabbed by another."); - } + } else if (rayPickInfo.overlayID) { + this.intersectionDistance = rayPickInfo.distance; + } else { + this.intersectionDistance = 0; + } + + var entity; + if (grabbableEntities.length > 0) { + // sort by distance + grabbableEntities.sort(function (a, b) { + var aDistance = Vec3.distance(entityPropertiesCache.getProps(a).position, handPosition); + var bDistance = Vec3.distance(entityPropertiesCache.getProps(b).position, handPosition); + return aDistance - bDistance; + }); + entity = grabbableEntities[0]; + name = entityPropertiesCache.getProps(entity).name; + this.grabbedEntity = entity; + if (this.entityWantsTrigger(entity)) { + if (this.triggerSmoothedGrab()) { + this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'"); return; + } else { + // potentialNearTriggerEntity = entity; } - this.temporaryPositionOffset = null; - if (!this.hasPresetOffsets()) { - // We want to give a temporary position offset to this object so it is pulled close to hand - var intersectionPointToCenterDistance = Vec3.length(Vec3.subtract(intersection.intersection, - intersection.properties.position)); - var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - var handJointPosition = MyAvatar.getJointPosition(handJointIndex); - this.temporaryPositionOffset = - Vec3.normalize(Vec3.subtract(intersection.properties.position, handJointPosition)); - this.temporaryPositionOffset = Vec3.multiply(this.temporaryPositionOffset, - intersectionPointToCenterDistance * - FAR_TO_NEAR_GRAB_PADDING_FACTOR); - } - this.setState(STATE_DISTANCE_HOLDING); - - this.searchSphereOff(); - return; - } - - // else this thing isn't physical. grab it by reparenting it (but not if we've already - // grabbed it). - if (refCount < 1) { - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // this.state == STATE_HOLD_SEARCHING) - this.setState(STATE_HOLD); - } - return; } else { - // it's not physical and it's already held via parenting. go ahead and grab it, but - // save off the current parent and joint. this wont always be right if there are more than - // two grabs and the order of release isn't opposite of the order of grabs. - this.shouldResetParentOnRelease = true; - this.previousParentID = props.parentID; - this.previousParentJointIndex = props.parentJointIndex; - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // (this.state == STATE_HOLD_SEARCHING) - this.setState(STATE_HOLD); + if (this.triggerSmoothedGrab()) { + var props = entityPropertiesCache.getProps(entity); + var grabProps = entityPropertiesCache.getGrabProps(entity); + var refCount = grabProps.refCount ? grabProps.refCount : 0; + if (refCount >= 1) { + // if another person is holding the object, remember to restore the + // parent info, when we are finished grabbing it. + this.shouldResetParentOnRelease = true; + this.previousParentID = props.parentID; + this.previousParentJointIndex = props.parentJointIndex; + } + + this.setState(STATE_NEAR_GRABBING, "near grab '" + name + "'"); + return; + } else { + // potentialNearGrabEntity = entity; } - return; - } - if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': fell through."); } } - //search line visualizations + if (rayPickInfo.entityID) { + entity = rayPickInfo.entityID; + name = entityPropertiesCache.getProps(entity).name; + if (this.entityWantsTrigger(entity)) { + if (this.triggerSmoothedGrab()) { + this.grabbedEntity = entity; + this.setState(STATE_FAR_TRIGGER, "far trigger '" + name + "'"); + return; + } else { + // potentialFarTriggerEntity = entity; + } + } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { + if (this.triggerSmoothedGrab() && !isEditing()) { + this.grabbedEntity = entity; + this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); + return; + } else { + // potentialFarGrabEntity = entity; + } + } + } + + this.updateEquipHaptics(potentialEquipHotspot); + + var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); + equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); + if (potentialEquipHotspot) { + equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); + } + + // search line visualizations if (USE_ENTITY_LINES_FOR_SEARCHING === true) { - this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + this.lineOn(rayPickInfo.searchRay.origin, + Vec3.multiply(rayPickInfo.searchRay.direction, LINE_LENGTH), + NO_INTERSECT_COLOR); } - this.searchIndicatorOn(distantPickRay); + this.searchIndicatorOn(rayPickInfo.searchRay); Reticle.setVisible(false); - }; - this.distanceGrabTimescale = function(mass, distance) { + this.distanceGrabTimescale = function (mass, distance) { var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / DISTANCE_HOLDING_UNITY_MASS * distance / DISTANCE_HOLDING_UNITY_DISTANCE; @@ -1099,22 +1576,27 @@ function MyController(hand) { timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; } return timeScale; - } + }; - this.getMass = function(dimensions, density) { + this.getMass = function (dimensions, density) { return (dimensions.x * dimensions.y * dimensions.z) * density; - } + }; - this.distanceHolding = function() { + this.distanceHoldingEnter = function () { + + this.clearEquipHaptics(); // controller pose is in avatar frame - var avatarControllerPose = - Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); + var device = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var avatarControllerPose = Controller.getPoseValue(device); // transform it into world frame - var controllerPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); - var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); + var worldControllerPosition = Vec3.sum(MyAvatar.position, + Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); + + // also transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var now = Date.now(); @@ -1125,7 +1607,7 @@ function MyController(hand) { this.currentObjectTime = now; this.currentCameraOrientation = Camera.orientation; - this.grabRadius = Vec3.distance(this.currentObjectPosition, controllerPosition); + this.grabRadius = Vec3.distance(this.currentObjectPosition, worldControllerPosition); this.grabRadialVelocity = 0.0; // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object @@ -1154,60 +1636,54 @@ function MyController(hand) { this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); if (this.actionID !== null) { - this.setState(STATE_CONTINUE_DISTANCE_HOLDING); this.activateEntity(this.grabbedEntity, grabbedProperties, false); this.callEntityMethodOnGrabbed("startDistanceGrab"); } this.turnOffVisualizations(); - this.previousControllerPosition = controllerPosition; - this.previousControllerRotation = controllerRotation; + this.previousRoomControllerPosition = roomControllerPosition; }; - this.continueDistanceHolding = function() { - if (this.triggerSmoothedReleased() && this.bumperReleased()) { - this.setState(STATE_RELEASE); + this.distanceHolding = function (deltaTime, timestamp) { + if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("releaseGrab"); + this.setState(STATE_OFF, "trigger released"); return; } this.heartBeat(this.grabbedEntity); // controller pose is in avatar frame - var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? - Controller.Standard.RightHand : Controller.Standard.LeftHand); + var device = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var avatarControllerPose = Controller.getPoseValue(device); // transform it into world frame - var controllerPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); - var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); + var worldControllerPosition = Vec3.sum(MyAvatar.position, + Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); + var worldControllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); + + // also transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var now = Date.now(); - var deltaTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds this.currentObjectTime = now; // the action was set up when this.distanceHolding was called. update the targets. - var radius = Vec3.distance(this.currentObjectPosition, controllerPosition) * + var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) * this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; if (radius < 1.0) { radius = 1.0; } - // scale delta controller hand movement by radius. - var handMoved = Vec3.multiply(Vec3.subtract(controllerPosition, this.previousControllerPosition), radius); - - // double delta controller rotation - var handChange = Quat.multiply(Quat.slerp(this.previousControllerRotation, - controllerRotation, - DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), - Quat.inverse(this.previousControllerRotation)); - - // update the currentObject position and rotation. + var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition); + var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta); + var handMoved = Vec3.multiply(worldHandDelta, radius); this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); - // this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); this.callEntityMethodOnGrabbed("continueDistantGrab"); @@ -1218,23 +1694,22 @@ function MyController(hand) { var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData); // Update radialVelocity - var lastVelocity = Vec3.subtract(controllerPosition, this.previousControllerPosition); - lastVelocity = Vec3.multiply(lastVelocity, 1.0 / deltaTime); - var newRadialVelocity = Vec3.dot(lastVelocity, - Vec3.normalize(Vec3.subtract(grabbedProperties.position, controllerPosition))); + var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime); + var delta = Vec3.normalize(Vec3.subtract(grabbedProperties.position, worldControllerPosition)); + var newRadialVelocity = Vec3.dot(lastVelocity, delta); var VELOCITY_AVERAGING_TIME = 0.016; - this.grabRadialVelocity = (deltaTime / VELOCITY_AVERAGING_TIME) * newRadialVelocity + - (1.0 - (deltaTime / VELOCITY_AVERAGING_TIME)) * this.grabRadialVelocity; + this.grabRadialVelocity = (deltaObjectTime / VELOCITY_AVERAGING_TIME) * newRadialVelocity + + (1.0 - (deltaObjectTime / VELOCITY_AVERAGING_TIME)) * this.grabRadialVelocity; var RADIAL_GRAB_AMPLIFIER = 10.0; if (Math.abs(this.grabRadialVelocity) > 0.0) { - this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaTime * this.grabRadius * RADIAL_GRAB_AMPLIFIER); + this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * + this.grabRadius * RADIAL_GRAB_AMPLIFIER); } - var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(controllerRotation)); - newTargetPosition = Vec3.sum(newTargetPosition, controllerPosition); - + var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position); if (handControllerData.disableMoveWithHead !== true) { @@ -1257,28 +1732,9 @@ function MyController(hand) { } } - var defaultConstraintData = { - axisStart: false, - axisEnd: false, - } - - var constraintData = getEntityCustomData('lightModifierKey', this.grabbedEntity, defaultConstraintData); - var clampedVector; - var targetPosition; - if (constraintData.axisStart !== false) { - clampedVector = this.projectVectorAlongAxis(this.currentObjectPosition, constraintData.axisStart, constraintData.axisEnd); - targetPosition = clampedVector; - } else { - targetPosition = { - x: this.currentObjectPosition.x, - y: this.currentObjectPosition.y, - z: this.currentObjectPosition.z - } - } - var handPosition = this.getHandPosition(); - //visualizations + // visualizations if (USE_ENTITY_LINES_FOR_MOVING === true) { this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); } @@ -1286,7 +1742,7 @@ function MyController(hand) { this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); } if (USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.handleDistantParticleBeam(handPosition, grabbedProperties.position, INTERSECT_COLOR) + this.handleDistantParticleBeam(handPosition, grabbedProperties.position, INTERSECT_COLOR); } if (USE_POINTLIGHT === true) { this.handlePointLight(this.grabbedEntity); @@ -1309,11 +1765,10 @@ function MyController(hand) { print("continueDistanceHolding -- updateAction failed"); } - this.previousControllerPosition = controllerPosition; - this.previousControllerRotation = controllerRotation; + this.previousRoomControllerPosition = roomControllerPosition; }; - this.setupHoldAction = function() { + this.setupHoldAction = function () { this.actionID = Entities.addAction("hold", this.grabbedEntity, { hand: this.hand === RIGHT_HAND ? "right" : "left", timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, @@ -1333,7 +1788,7 @@ function MyController(hand) { return true; }; - this.projectVectorAlongAxis = function(position, axisStart, axisEnd) { + this.projectVectorAlongAxis = function (position, axisStart, axisEnd) { var aPrime = Vec3.subtract(position, axisStart); var bPrime = Vec3.subtract(axisEnd, axisStart); var bPrimeMagnitude = Vec3.length(bPrime); @@ -1346,62 +1801,63 @@ function MyController(hand) { scalar = 1; } var projection = Vec3.sum(axisStart, Vec3.multiply(scalar, Vec3.normalize(bPrime))); - return projection + return projection; }; - this.hasPresetOffsets = function() { - var wearableData = getEntityCustomData('wearable', this.grabbedEntity, {joints: {}}); - if ("joints" in wearableData) { - var allowedJoints = wearableData.joints; - var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; - if (handJointName in allowedJoints) { - return true; - } - } - return false; - } + this.dropGestureReset = function () { + this.prevHandIsUpsideDown = false; + }; - this.getPresetPosition = function() { - var wearableData = getEntityCustomData('wearable', this.grabbedEntity, {joints: {}}); - var allowedJoints = wearableData.joints; - var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; - if (handJointName in allowedJoints) { - return allowedJoints[handJointName][0]; - } - } + this.dropGestureProcess = function (deltaTime) { + var standardControllerValue = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var pose = Controller.getPoseValue(standardControllerValue); + var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); - this.getPresetRotation = function() { - var wearableData = getEntityCustomData('wearable', this.grabbedEntity, {joints: {}}); - var allowedJoints = wearableData.joints; - var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; - if (handJointName in allowedJoints) { - return allowedJoints[handJointName][1]; - } - } + var localHandUpAxis = this.hand === RIGHT_HAND ? {x: 1, y: 0, z: 0} : {x: -1, y: 0, z: 0}; + var worldHandUpAxis = Vec3.multiplyQbyV(worldHandRotation, localHandUpAxis); + var DOWN = {x: 0, y: -1, z: 0}; - this.nearGrabbing = function() { - var now = Date.now(); + var DROP_ANGLE = Math.PI / 7; + var HYSTERESIS_FACTOR = 1.1; + var ROTATION_ENTER_THRESHOLD = Math.cos(DROP_ANGLE); + var ROTATION_EXIT_THRESHOLD = Math.cos(DROP_ANGLE * HYSTERESIS_FACTOR); + var rotationThreshold = this.prevHandIsUpsideDown ? ROTATION_EXIT_THRESHOLD : ROTATION_ENTER_THRESHOLD; - if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - this.callEntityMethodOnGrabbed("releaseGrab"); - return; + var handIsUpsideDown = false; + if (Vec3.dot(worldHandUpAxis, DOWN) > rotationThreshold) { + handIsUpsideDown = true; } - if (this.state == STATE_HOLD && this.bumperReleased()) { - this.setState(STATE_RELEASE); - this.callEntityMethodOnGrabbed("releaseGrab"); - return; + + if (handIsUpsideDown != this.prevHandIsUpsideDown) { + this.prevHandIsUpsideDown = handIsUpsideDown; + Controller.triggerShortHapticPulse(0.5, this.hand); } + return handIsUpsideDown; + }; + + this.nearGrabbingEnter = function () { + this.lineOff(); this.overlayLineOff(); + this.dropGestureReset(); + this.clearEquipHaptics(); + + Controller.triggerShortHapticPulse(1.0, this.hand); + if (this.entityActivated) { var saveGrabbedID = this.grabbedEntity; this.release(); this.grabbedEntity = saveGrabbedID; } + var otherHandController = this.getOtherHandController(); + if (otherHandController.grabbedEntity == this.grabbedEntity && + (otherHandController.state == STATE_NEAR_GRABBING || otherHandController.state == STATE_DISTANCE_HOLDING)) { + otherHandController.setState(STATE_OFF, "other hand grabbed this entity"); + } + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); this.activateEntity(this.grabbedEntity, grabbedProperties, false); @@ -1410,13 +1866,24 @@ function MyController(hand) { var handPosition = this.getHandPosition(); var hasPresetPosition = false; - if ((this.state == STATE_EQUIP || this.state == STATE_HOLD) && this.hasPresetOffsets()) { + if (this.state == STATE_HOLD && this.grabbedHotspot) { var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); // if an object is "equipped" and has a predefined offset, use it. this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; - this.offsetPosition = this.getPresetPosition(); - this.offsetRotation = this.getPresetRotation(); - hasPresetPosition = true; + + var offsets = USE_ATTACH_POINT_SETTINGS && getAttachPointForHotspotFromSettings(this.grabbedHotspot, this.hand); + if (offsets) { + this.offsetPosition = offsets[0]; + this.offsetRotation = offsets[1]; + hasPresetPosition = true; + } else { + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (this.grabbedHotspot.joints[handJointName]) { + this.offsetPosition = this.grabbedHotspot.joints[handJointName][0]; + this.offsetRotation = this.grabbedHotspot.joints[handJointName][1]; + hasPresetPosition = true; + } + } } else { this.ignoreIK = false; @@ -1426,13 +1893,13 @@ function MyController(hand) { var currentObjectPosition = grabbedProperties.position; var offset = Vec3.subtract(currentObjectPosition, handPosition); this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); - if (this.temporaryPositionOffset && (this.state == STATE_EQUIP)) { + if (this.temporaryPositionOffset) { this.offsetPosition = this.temporaryPositionOffset; // hasPresetPosition = true; } } - var isPhysical = this.propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); + var isPhysical = propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); if (isPhysical && this.state == STATE_NEAR_GRABBING) { // grab entity via action if (!this.setupHoldAction()) { @@ -1446,10 +1913,10 @@ function MyController(hand) { // grab entity via parenting this.actionID = null; var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - reparentProps = { + var reparentProps = { parentID: MyAvatar.sessionUUID, parentJointIndex: handJointIndex - } + }; if (hasPresetPosition) { reparentProps["localPosition"] = this.offsetPosition; reparentProps["localRotation"] = this.offsetRotation; @@ -1470,21 +1937,10 @@ function MyController(hand) { if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("startNearGrab"); - } else { // this.state == STATE_EQUIP || this.state == STATE_HOLD + } else { // this.state == STATE_HOLD this.callEntityMethodOnGrabbed("startEquip"); } - if (this.state == STATE_NEAR_GRABBING) { - // near grabbing - this.setState(STATE_CONTINUE_NEAR_GRABBING); - } else if (this.state == STATE_HOLD) { - // holding - this.setState(STATE_CONTINUE_HOLD); - } else { // (this.state == STATE_EQUIP) - // equipping - this.setState(STATE_CONTINUE_EQUIP); - } - this.currentHandControllerTipPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; this.currentObjectTime = Date.now(); @@ -1493,33 +1949,55 @@ function MyController(hand) { this.currentObjectRotation = grabbedProperties.rotation; this.currentVelocity = ZERO_VEC; this.currentAngularVelocity = ZERO_VEC; + + this.prevDropDetected = false; }; - this.continueNearGrabbing = function() { - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); + this.nearGrabbing = function (deltaTime, timestamp) { + + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("releaseGrab"); + this.setState(STATE_OFF, "trigger released"); return; } - if (this.state == STATE_CONTINUE_HOLD && this.bumperReleased()) { - this.setState(STATE_RELEASE); - this.callEntityMethodOnGrabbed("releaseEquip"); - return; - } - if (this.state == STATE_CONTINUE_EQUIP && this.thumbPressed()) { - this.setState(STATE_WAITING_FOR_RELEASE_THUMB_RELEASE); - this.callEntityMethodOnGrabbed("releaseEquip"); - return; - } - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.thumbPressed()) { - this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE); - this.callEntityMethodOnGrabbed("releaseGrab"); - this.callEntityMethodOnGrabbed("startEquip"); - return; - } - if (this.state == STATE_CONTINUE_HOLD && this.thumbPressed()) { - this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE); - return; + + if (this.state == STATE_HOLD) { + + var dropDetected = this.dropGestureProcess(deltaTime); + + if (this.triggerSmoothedReleased()) { + this.waitForTriggerRelease = false; + } + + if (dropDetected && this.prevDropDetected != dropDetected) { + this.waitForTriggerRelease = true; + } + + // highlight the grabbed hotspot when the dropGesture is detected. + if (dropDetected) { + entityPropertiesCache.addEntity(this.grabbedHotspot.entityID); + equipHotspotBuddy.updateHotspot(this.grabbedHotspot, timestamp); + equipHotspotBuddy.highlightHotspot(this.grabbedHotspot); + } + + if (dropDetected && !this.waitForTriggerRelease && this.triggerSmoothedGrab()) { + this.callEntityMethodOnGrabbed("releaseEquip"); + + // store the offset attach points into preferences. + if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedEntity) { + var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + if (props && props.localPosition && props.localRotation) { + storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, props.localPosition, props.localRotation); + } + } + + var grabbedEntity = this.grabbedEntity; + this.release(); + this.grabbedEntity = grabbedEntity; + this.setState(STATE_NEAR_GRABBING, "drop gesture detected"); + return; + } + this.prevDropDetected = dropDetected; } this.heartBeat(this.grabbedEntity); @@ -1527,32 +2005,32 @@ function MyController(hand) { var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position", "rotation"]); if (!props.position) { // server may have reset, taking our equipped entity with it. move back to "off" stte - this.setState(STATE_RELEASE); this.callEntityMethodOnGrabbed("releaseGrab"); + this.setState(STATE_OFF, "entity has no position property"); return; } - var now = Date.now(); if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) { this.lastUnequipCheckTime = now; if (props.parentID == MyAvatar.sessionUUID && - Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) { + Vec3.length(props.localPosition) > NEAR_GRAB_MAX_DISTANCE) { var handPosition = this.getHandPosition(); // the center of the equipped object being far from the hand isn't enough to autoequip -- we also // need to fail the findEntities test. - nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + " " + vec3toStr(props.position)); - this.setState(STATE_RELEASE); - if (this.state == STATE_CONTINUE_NEAR_GRABBING) { + + if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); - } else { // (this.state == STATE_CONTINUE_EQUIP || this.state == STATE_CONTINUE_HOLD) + } else { // this.state == STATE_HOLD this.callEntityMethodOnGrabbed("releaseEquip"); } + this.setState(STATE_OFF, "held object too far away"); return; } } @@ -1566,20 +2044,18 @@ function MyController(hand) { // from the palm. var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var now = Date.now(); - var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters - var deltaTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds - if (deltaTime > 0.0) { + if (deltaObjectTime > 0.0) { var worldDeltaPosition = Vec3.subtract(props.position, this.currentObjectPosition); var previousEulers = Quat.safeEulerAngles(this.currentObjectRotation); var newEulers = Quat.safeEulerAngles(props.rotation); var worldDeltaRotation = Vec3.subtract(newEulers, previousEulers); - this.currentVelocity = Vec3.multiply(worldDeltaPosition, 1.0 / deltaTime); - this.currentAngularVelocity = Vec3.multiply(worldDeltaRotation, Math.PI / (deltaTime * 180.0)); + this.currentVelocity = Vec3.multiply(worldDeltaPosition, 1.0 / deltaObjectTime); + this.currentAngularVelocity = Vec3.multiply(worldDeltaRotation, Math.PI / (deltaObjectTime * 180.0)); this.currentObjectPosition = props.position; this.currentObjectRotation = props.rotation; @@ -1588,11 +2064,10 @@ function MyController(hand) { this.currentHandControllerTipPosition = handControllerPosition; this.currentObjectTime = now; - var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); - if (this.state === STATE_CONTINUE_EQUIP || this.state === STATE_CONTINUE_HOLD) { + if (this.state === STATE_HOLD) { this.callEntityMethodOnGrabbed("continueEquip"); } - if (this.state == STATE_CONTINUE_NEAR_GRABBING) { + if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("continueNearGrab"); } @@ -1618,50 +2093,33 @@ function MyController(hand) { } }; - this.waitingForEquipThumbRelease = function() { - if (this.thumbReleased() && this.triggerSmoothedReleased()) { - this.setState(STATE_EQUIP); - } - }; - this.waitingForReleaseThumbRelease = function() { - if (this.thumbReleased() && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - } - }; + this.nearTriggerEnter = function () { - this.nearTrigger = function() { - if (this.triggerSmoothedReleased() && this.bumperReleased()) { - this.setState(STATE_RELEASE); - this.callEntityMethodOnGrabbed("stopNearTrigger"); - return; - } + this.clearEquipHaptics(); + + Controller.triggerShortHapticPulse(1.0, this.hand); this.callEntityMethodOnGrabbed("startNearTrigger"); - this.setState(STATE_CONTINUE_NEAR_TRIGGER); }; - this.farTrigger = function() { - if (this.triggerSmoothedReleased() && this.bumperReleased()) { - this.setState(STATE_RELEASE); - this.callEntityMethodOnGrabbed("stopFarTrigger"); - return; - } + this.farTriggerEnter = function () { + this.clearEquipHaptics(); + this.callEntityMethodOnGrabbed("startFarTrigger"); - this.setState(STATE_CONTINUE_FAR_TRIGGER); }; - this.continueNearTrigger = function() { - if (this.triggerSmoothedReleased() && this.bumperReleased()) { - this.setState(STATE_RELEASE); + this.nearTrigger = function (deltaTime, timestamp) { + if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("stopNearTrigger"); + this.setState(STATE_OFF, "trigger released"); return; } this.callEntityMethodOnGrabbed("continueNearTrigger"); }; - this.continueFarTrigger = function() { - if (this.triggerSmoothedReleased() && this.bumperReleased()) { - this.setState(STATE_RELEASE); + this.farTrigger = function (deltaTime, timestamp) { + if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("stopFarTrigger"); + this.setState(STATE_OFF, "trigger released"); return; } @@ -1673,12 +2131,12 @@ function MyController(hand) { var now = Date.now(); if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - var intersection = Entities.findRayIntersection(pickRay, true); - if (intersection.accurate) { + var intersection = findRayIntersection(pickRay, true); + if (intersection.accurate || intersection.overlayID) { this.lastPickTime = now; if (intersection.entityID != this.grabbedEntity) { - this.setState(STATE_RELEASE); this.callEntityMethodOnGrabbed("stopFarTrigger"); + this.setState(STATE_OFF, "laser moved off of entity"); return; } if (intersection.intersects) { @@ -1691,16 +2149,34 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("continueFarTrigger"); }; - this.release = function() { + this.offEnter = function () { + this.release(); + }; + + this.release = function () { this.turnLightsOff(); this.turnOffVisualizations(); var noVelocity = false; if (this.grabbedEntity !== null) { + + // If this looks like the release after adjusting something still held in the other hand, print the position + // and rotation of the held thing to help content creators set the userData. + var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); + if (grabData.refCount > 1) { + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + if (grabbedProperties && grabbedProperties.localPosition && grabbedProperties.localRotation) { + print((this.hand === RIGHT_HAND ? '"LeftHand"' : '"RightHand"') + ":" + + '[{"x":' + grabbedProperties.localPosition.x + ', "y":' + grabbedProperties.localPosition.y + + ', "z":' + grabbedProperties.localPosition.z + '}, {"x":' + grabbedProperties.localRotation.x + + ', "y":' + grabbedProperties.localRotation.y + ', "z":' + grabbedProperties.localRotation.z + + ', "w":' + grabbedProperties.localRotation.w + '}]'); + } + } + if (this.actionID !== null) { Entities.deleteAction(this.grabbedEntity, this.actionID); // sometimes we want things to stay right where they are when we let go. - var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); var releaseVelocityData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); if (releaseVelocityData.disableReleaseVelocity === true || // this next line allowed both: @@ -1714,7 +2190,6 @@ function MyController(hand) { this.deactivateEntity(this.grabbedEntity, noVelocity); this.actionID = null; - this.setState(STATE_OFF); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'release', @@ -1723,16 +2198,21 @@ function MyController(hand) { })); this.grabbedEntity = null; + this.grabbedHotspot = null; + + if (this.triggerSmoothedGrab()) { + this.waitForTriggerRelease = true; + } }; - this.cleanup = function() { + this.cleanup = function () { this.release(); Entities.deleteEntity(this.particleBeamObject); Entities.deleteEntity(this.spotLight); Entities.deleteEntity(this.pointLight); }; - this.heartBeat = function(entityID) { + this.heartBeat = function (entityID) { var now = Date.now(); if (now - this.lastHeartBeat > HEART_BEAT_INTERVAL) { var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); @@ -1742,7 +2222,7 @@ function MyController(hand) { } }; - this.resetAbandonedGrab = function(entityID) { + this.resetAbandonedGrab = function (entityID) { print("cleaning up abandoned grab on " + entityID); var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); data["refCount"] = 1; @@ -1750,12 +2230,23 @@ function MyController(hand) { this.deactivateEntity(entityID, false); }; - this.activateEntity = function(entityID, grabbedProperties, wasLoaded) { + this.activateEntity = function (entityID, grabbedProperties, wasLoaded) { if (this.entityActivated) { return; } this.entityActivated = true; + if (delayedDeactivateTimeout && delayedDeactivateEntityID == entityID) { + // we have a timeout waiting to set collisions with myAvatar back on (so that when something + // is thrown it doesn't collide with the avatar's capsule the moment it's released). We've + // regrabbed the entity before the timeout fired, so cancel the timeout, run the function now + // and adjust the grabbedProperties. This will make the saved set of properties (the ones that + // get re-instated after all the grabs have been released) be correct. + Script.clearTimeout(delayedDeactivateTimeout); + delayedDeactivateTimeout = null; + grabbedProperties["collidesWith"] = delayedDeactivateFunc(); + } + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); var now = Date.now(); @@ -1812,44 +2303,84 @@ function MyController(hand) { return data; }; - this.checkForStrayChildren = function() { + this.checkForStrayChildren = function () { // sometimes things can get parented to a hand and this script is unaware. Search for such entities and // unhook them. var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex); - children.forEach(function(childID) { + children.forEach(function (childID) { print("disconnecting stray child of hand: (" + _this.hand + ") " + childID); Entities.editEntity(childID, {parentID: NULL_UUID}); }); - } + }; + + this.delayedDeactivateEntity = function (entityID, collidesWith) { + // If, before the grab started, the held entity collided with myAvatar, we do the deactivation in + // two parts. Most of it is done in deactivateEntity(), but the final collidesWith and refcount + // are delayed a bit. This keeps thrown things from colliding with the avatar's capsule so often. + // The refcount is handled in this delayed fashion so things don't get confused if someone else + // grabs the entity before the timeout fires. + Entities.editEntity(entityID, { collidesWith: collidesWith }); + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + if (data && data["refCount"]) { + data["refCount"] = data["refCount"] - 1; + if (data["refCount"] < 1) { + data = null; + } + } else { + data = null; + } + + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + }; + + this.deactivateEntity = function (entityID, noVelocity, delayed) { + var deactiveProps; - this.deactivateEntity = function(entityID, noVelocity) { if (!this.entityActivated) { return; } this.entityActivated = false; var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + var doDelayedDeactivate = false; if (data && data["refCount"]) { data["refCount"] = data["refCount"] - 1; if (data["refCount"] < 1) { - var deactiveProps = { + deactiveProps = { gravity: data["gravity"], - collidesWith: data["collidesWith"], + // don't set collidesWith myAvatar back right away, because thrown things tend to bounce off the + // avatar's capsule. + collidesWith: removeMyAvatarFromCollidesWith(data["collidesWith"]), collisionless: data["collisionless"], dynamic: data["dynamic"], parentID: data["parentID"], parentJointIndex: data["parentJointIndex"] }; + doDelayedDeactivate = (data["collidesWith"].indexOf("myAvatar") >= 0); + + if (doDelayedDeactivate) { + var delayedCollidesWith = data["collidesWith"]; + var delayedEntityID = entityID; + delayedDeactivateFunc = function () { + // set collidesWith back to original value a bit later than the rest + delayedDeactivateTimeout = null; + _this.delayedDeactivateEntity(delayedEntityID, delayedCollidesWith); + return delayedCollidesWith; + }; + delayedDeactivateTimeout = + Script.setTimeout(delayedDeactivateFunc, COLLIDE_WITH_AV_AFTER_RELEASE_DELAY * MSECS_PER_SEC); + delayedDeactivateEntityID = entityID; + } + // things that are held by parenting and dropped with no velocity will end up as "static" in bullet. If // it looks like the dropped thing should fall, give it a little velocity. - var props = Entities.getEntityProperties(entityID, ["parentID", "velocity", "dynamic", "shapeType"]) + var props = Entities.getEntityProperties(entityID, ["parentID", "velocity", "dynamic", "shapeType"]); var parentID = props.parentID; - var forceVelocity = false; var doSetVelocity = false; - if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID && this.propsArePhysical(props)) { + if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID && propsArePhysical(props)) { // TODO: EntityScriptingInterface::convertLocationToScriptSemantics should be setting up // props.velocity to be a world-frame velocity and localVelocity to be vs parent. Until that // is done, we use a measured velocity here so that things held via a bumper-grab / parenting-grab @@ -1881,15 +2412,14 @@ function MyController(hand) { // the parent causes it to go off in the wrong direction. This is a bug that should // be fixed. Entities.editEntity(entityID, { - velocity: this.currentVelocity, + velocity: this.currentVelocity // angularVelocity: this.currentAngularVelocity }); } - data = null; } else if (this.shouldResetParentOnRelease) { // we parent-grabbed this from another parent grab. try to put it back where we found it. - var deactiveProps = { + deactiveProps = { parentID: this.previousParentID, parentJointIndex: this.previousParentJointIndex, velocity: {x: 0.0, y: 0.0, z: 0.0}, @@ -1904,31 +2434,15 @@ function MyController(hand) { } else { data = null; } - setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + if (!doDelayedDeactivate) { + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + } }; - this.checkNewlyLoaded = function(loadedEntityID) { - if (this.state == STATE_OFF || - this.state == STATE_SEARCHING || - this.state == STATE_HOLD_SEARCHING) { - var loadedProps = Entities.getEntityProperties(loadedEntityID); - if (loadedProps.parentID != MyAvatar.sessionUUID) { - return; - } - var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - if (loadedProps.parentJointIndex != handJointIndex) { - return; - } - print("--- handControllerGrab found loaded entity ---"); - // an entity has been loaded and it's where this script would have equipped something, so switch states. - this.grabbedEntity = loadedEntityID; - this.activateEntity(this.grabbedEntity, loadedProps, true); - this.isInitialGrab = true; - this.callEntityMethodOnGrabbed("startEquip"); - this.setState(STATE_CONTINUE_EQUIP); - } - } -}; + this.getOtherHandController = function () { + return (this.hand === RIGHT_HAND) ? leftController : rightController; + }; +} var rightController = new MyController(RIGHT_HAND); var leftController = new MyController(LEFT_HAND); @@ -1937,26 +2451,36 @@ var MAPPING_NAME = "com.highfidelity.handControllerGrab"; var mapping = Controller.newMapping(MAPPING_NAME); mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); -mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); +mapping.from([Controller.Standard.RTClick]).peek().to(rightController.triggerClick); -mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress); -mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress); +mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); +mapping.from([Controller.Standard.LTClick]).peek().to(leftController.triggerClick); + +mapping.from([Controller.Standard.RB]).peek().to(rightController.secondaryPress); +mapping.from([Controller.Standard.LB]).peek().to(leftController.secondaryPress); +mapping.from([Controller.Standard.LeftGrip]).peek().to(leftController.secondaryPress); +mapping.from([Controller.Standard.RightGrip]).peek().to(rightController.secondaryPress); mapping.from([Controller.Standard.LeftPrimaryThumb]).peek().to(leftController.thumbPress); mapping.from([Controller.Standard.RightPrimaryThumb]).peek().to(rightController.thumbPress); Controller.enableMapping(MAPPING_NAME); -//the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items +// the section below allows the grab script to listen for messages +// that disable either one or both hands. useful for two handed items var handToDisable = 'none'; -function update() { +function update(deltaTime) { + var timestamp = Date.now(); + if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { - leftController.update(); + leftController.update(deltaTime, timestamp); } if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { - rightController.update(); + rightController.update(deltaTime, timestamp); } + equipHotspotBuddy.update(deltaTime, timestamp); + entityPropertiesCache.update(); } Messages.subscribe('Hifi-Hand-Disabler'); @@ -1964,31 +2488,41 @@ Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.subscribe('Hifi-Object-Manipulation'); -handleHandMessages = function(channel, message, sender) { +var handleHandMessages = function (channel, message, sender) { + var data; if (sender === MyAvatar.sessionUUID) { if (channel === 'Hifi-Hand-Disabler') { if (message === 'left') { handToDisable = LEFT_HAND; + leftController.turnOffVisualizations(); } if (message === 'right') { handToDisable = RIGHT_HAND; + rightController.turnOffVisualizations(); } if (message === 'both' || message === 'none') { + if (message === 'both') { + rightController.turnOffVisualizations(); + leftController.turnOffVisualizations(); + + } handToDisable = message; } } else if (channel === 'Hifi-Hand-Grab') { try { - var data = JSON.parse(message); + data = JSON.parse(message); var selectedController = (data.hand === 'left') ? leftController : rightController; selectedController.release(); - selectedController.setState(STATE_EQUIP); + selectedController.setState(STATE_HOLD, "Hifi-Hand-Grab msg received"); selectedController.grabbedEntity = data.entityID; - } catch (e) {} + } catch (e) { + print("WARNING: error parsing Hifi-Hand-Grab message"); + } } else if (channel === 'Hifi-Hand-RayPick-Blacklist') { try { - var data = JSON.parse(message); + data = JSON.parse(message); var action = data.action; var id = data.id; var index = blacklist.indexOf(id); @@ -2002,27 +2536,12 @@ handleHandMessages = function(channel, message, sender) { } } - } catch (e) {} - } else if (channel === 'Hifi-Object-Manipulation') { - if (sender !== MyAvatar.sessionUUID) { - return; - } - - var parsedMessage = null; - try { - parsedMessage = JSON.parse(message); } catch (e) { - print('error parsing Hifi-Object-Manipulation message'); - return; - } - - if (parsedMessage.action === 'loaded') { - rightController.checkNewlyLoaded(parsedMessage['grabbedEntity']); - leftController.checkNewlyLoaded(parsedMessage['grabbedEntity']); + print("WARNING: error parsing Hifi-Hand-RayPick-Blacklist message"); } } } -} +}; Messages.messageReceived.connect(handleHandMessages); @@ -2032,5 +2551,6 @@ function cleanup() { Controller.disableMapping(MAPPING_NAME); Reticle.setVisible(true); } + Script.scriptEnding.connect(cleanup); Script.update.connect(update); diff --git a/scripts/system/controllers/handControllerMouse.js b/scripts/system/controllers/handControllerMouse.js deleted file mode 100644 index 921999f96a..0000000000 --- a/scripts/system/controllers/handControllerMouse.js +++ /dev/null @@ -1,131 +0,0 @@ -// -// handControllerMouse.js -// examples/controllers -// -// Created by Brad Hefta-Gaub on 2015/12/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 DEBUGGING = false; -var angularVelocityTrailingAverage = 0.0; // Global trailing average used to decide whether to move reticle at all -var lastX = 0; -var lastY = 0; - -Math.clamp=function(a,b,c) { - return Math.max(b,Math.min(c,a)); -} - -function length(posA, posB) { - var dx = posA.x - posB.x; - var dy = posA.y - posB.y; - var length = Math.sqrt((dx*dx) + (dy*dy)) - return length; -} - -function moveReticleAbsolute(x, y) { - var globalPos = Reticle.getPosition(); - globalPos.x = x; - globalPos.y = y; - Reticle.setPosition(globalPos); -} - -var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation"; -var mapping = Controller.newMapping(MAPPING_NAME); -if (Controller.Hardware.Hydra !== undefined) { - mapping.from(Controller.Hardware.Hydra.L3).peek().to(Controller.Actions.ReticleClick); - mapping.from(Controller.Hardware.Hydra.R4).peek().to(Controller.Actions.ReticleClick); -} -if (Controller.Hardware.Vive !== undefined) { - mapping.from(Controller.Hardware.Vive.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick); - mapping.from(Controller.Hardware.Vive.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick); -} - -mapping.enable(); - -function debugPrint(message) { - if (DEBUGGING) { - print(message); - } -} - -var leftRightBias = 0.0; -var filteredRotatedLeft = Vec3.UNIT_NEG_Y; -var filteredRotatedRight = Vec3.UNIT_NEG_Y; -var lastAlpha = 0; - -Script.update.connect(function(deltaTime) { - - // avatar frame - var poseRight = Controller.getPoseValue(Controller.Standard.RightHand); - var poseLeft = Controller.getPoseValue(Controller.Standard.LeftHand); - - // NOTE: hack for now - var screenSize = Reticle.maximumPosition; - var screenSizeX = screenSize.x; - var screenSizeY = screenSize.y; - - // transform hand facing vectors from avatar frame into sensor frame. - var worldToSensorMatrix = Mat4.inverse(MyAvatar.sensorToWorldMatrix); - var rotatedRight = Mat4.transformVector(worldToSensorMatrix, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y))); - var rotatedLeft = Mat4.transformVector(worldToSensorMatrix, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y))); - - lastRotatedRight = rotatedRight; - - // Decide which hand should be controlling the pointer - // by comparing which one is moving more, and by - // tending to stay with the one moving more. - if (deltaTime > 0.001) { - // leftRightBias is a running average of the difference in angular hand speed. - // a positive leftRightBias indicates the right hand is spinning faster then the left hand. - // a negative leftRightBias indicates the left hand is spnning faster. - var BIAS_ADJUST_PERIOD = 1.0; - var tau = Math.clamp(deltaTime / BIAS_ADJUST_PERIOD, 0, 1); - newLeftRightBias = Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity); - leftRightBias = (1 - tau) * leftRightBias + tau * newLeftRightBias; - } - - // add a bit of hysteresis to prevent control flopping back and forth - // between hands when they are both mostly stationary. - var alpha; - var HYSTERESIS_OFFSET = 0.25; - if (lastAlpha > 0.5) { - // prefer right hand over left - alpha = leftRightBias > -HYSTERESIS_OFFSET ? 1 : 0; - } else { - alpha = leftRightBias > HYSTERESIS_OFFSET ? 1 : 0; - } - lastAlpha = alpha; - - // Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers - var VELOCITY_FILTER_GAIN = 0.5; - filteredRotatedLeft = Vec3.mix(filteredRotatedLeft, rotatedLeft, Math.clamp(Vec3.length(poseLeft.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0)); - filteredRotatedRight = Vec3.mix(filteredRotatedRight, rotatedRight, Math.clamp(Vec3.length(poseRight.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0)); - var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, alpha); - - var absolutePitch = rotated.y; // from 1 down to -1 up ... but note: if you rotate down "too far" it starts to go up again... - var absoluteYaw = -rotated.x; // from -1 left to 1 right - - var x = Math.clamp(screenSizeX * (absoluteYaw + 0.5), 0, screenSizeX); - var y = Math.clamp(screenSizeX * absolutePitch, 0, screenSizeY); - - // don't move the reticle with the hand controllers unless the controllers are actually being moved - // take a time average of angular velocity, and don't move mouse at all if it's below threshold - - var AVERAGING_INTERVAL = 0.95; - var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.03; - var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - alpha) + Vec3.length(poseRight.angularVelocity) * alpha; - angularVelocityTrailingAverage = angularVelocityTrailingAverage * AVERAGING_INTERVAL + angularVelocityMagnitude * (1.0 - AVERAGING_INTERVAL); - - if ((angularVelocityTrailingAverage > MINIMUM_CONTROLLER_ANGULAR_VELOCITY) && ((x != lastX) || (y != lastY))) { - moveReticleAbsolute(x, y); - lastX = x; - lastY = y; - } -}); - -Script.scriptEnding.connect(function(){ - mapping.disable(); -}); diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 921c55b7b2..dab6438efa 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -1,6 +1,6 @@ "use strict"; /*jslint vars: true, plusplus: true*/ -/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print */ +/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print*/ // // handControllerPointer.js @@ -14,22 +14,16 @@ // // Control the "mouse" using hand controller. (HMD and desktop.) -// For now: -// Hydra thumb button 3 is left-mouse, button 4 is right-mouse. -// A click in the center of the vive thumb pad is left mouse. Vive menu button is context menu (right mouse). // First-person only. // Starts right handed, but switches to whichever is free: Whichever hand was NOT most recently squeezed. // (For now, the thumb buttons on both controllers are always on.) -// When over a HUD element, the reticle is shown where the active hand controller beam intersects the HUD. -// Otherwise, the active hand controller shows a red ball where a click will act. -// -// Bugs: -// On Windows, the upper left corner of Interface must be in the upper left corner of the screen, and the title bar must be 50px high. (System bug.) -// While hardware mouse move switches to mouse move, hardware mouse click (without amove) does not. +// When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand +// controller beam intersects the HUD. // UTILITIES ------------- // +function ignore() { } // Utility to make it easier to setup and disconnect cleanly. function setupHandler(event, handler) { @@ -38,6 +32,7 @@ function setupHandler(event, handler) { event.disconnect(handler); }); } + // If some capability is not available until expiration milliseconds after the last update. function TimeLock(expiration) { var last = 0; @@ -48,23 +43,64 @@ function TimeLock(expiration) { return ((optionalNow || Date.now()) - last) > expiration; }; } + var handControllerLockOut = new TimeLock(2000); -// Calls onFunction() or offFunction() when swtich(on), but only if it is to a new value. -function LatchedToggle(onFunction, offFunction, state) { - this.getState = function () { - return state; +function Trigger(label) { + // This part is copied and adapted from handControllerGrab.js. Maybe we should refactor this. + var that = this; + that.label = label; + that.TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing + that.TRIGGER_OFF_VALUE = 0.10; + that.TRIGGER_ON_VALUE = that.TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab + that.rawTriggerValue = 0; + that.triggerValue = 0; // rolling average of trigger value + that.triggerClicked = false; + that.triggerClick = function (value) { that.triggerClicked = value; }; + that.triggerPress = function (value) { that.rawTriggerValue = value; }; + that.updateSmoothedTrigger = function () { // e.g., call once/update for effect + var triggerValue = that.rawTriggerValue; + // smooth out trigger value + that.triggerValue = (that.triggerValue * that.TRIGGER_SMOOTH_RATIO) + + (triggerValue * (1.0 - that.TRIGGER_SMOOTH_RATIO)); + OffscreenFlags.navigationFocusDisabled = that.triggerValue != 0.0; }; - this.setState = function (on) { - if (state === on) { - return; - } - state = on; - if (on) { - onFunction(); - } else { - offFunction(); + // Current smoothed state, without hysteresis. Answering booleans. + that.triggerSmoothedClick = function () { + return that.triggerClicked; + }; + that.triggerSmoothedSqueezed = function () { + return that.triggerValue > that.TRIGGER_ON_VALUE; + }; + that.triggerSmoothedReleased = function () { + return that.triggerValue < that.TRIGGER_OFF_VALUE; + }; + + // This part is not from handControllerGrab.js + that.state = null; // tri-state: falsey, 'partial', 'full' + that.update = function () { // update state, called from an update function + var state = that.state; + that.updateSmoothedTrigger(); + + // The first two are independent of previous state: + if (that.triggerSmoothedClick()) { + state = 'full'; + } else if (that.triggerSmoothedReleased()) { + state = null; + } else if (that.triggerSmoothedSqueezed()) { + // Another way to do this would be to have hysteresis in this branch, but that seems to make things harder to use. + // In particular, the vive has a nice detent as you release off of full, and we want that to be a transition from + // full to partial. + state = 'partial'; } + that.state = state; + }; + // Answer a controller source function (answering either 0.0 or 1.0). + that.partial = function () { + return that.state ? 1.0 : 0.0; // either 'partial' or 'full' + }; + that.full = function () { + return (that.state === 'full') ? 1.0 : 0.0; }; } @@ -84,6 +120,10 @@ function ignoreMouseActivity() { if (!Reticle.allowMouseCapture) { return true; } + var pos = Reticle.position; + if (pos.x == -1 && pos.y == -1) { + return true; + } // Only we know if we moved it, which is why this script has to replace depthReticle.js if (!weMovedReticle) { return false; @@ -91,8 +131,17 @@ function ignoreMouseActivity() { weMovedReticle = false; return true; } +var MARGIN = 25; +var reticleMinX = MARGIN, reticleMaxX, reticleMinY = MARGIN, reticleMaxY; +function updateRecommendedArea() { + var dims = Controller.getViewportDimensions(); + reticleMaxX = dims.x - MARGIN; + reticleMaxY = dims.y - MARGIN; +} var setReticlePosition = function (point2d) { weMovedReticle = true; + point2d.x = Math.max(reticleMinX, Math.min(point2d.x, reticleMaxX)); + point2d.y = Math.max(reticleMinY, Math.min(point2d.y, reticleMaxY)); Reticle.setPosition(point2d); }; @@ -110,9 +159,8 @@ function isPointingAtOverlay(optionalHudPosition2d) { } // Generalized HUD utilities, with or without HMD: -// These two "vars" are for documentation. Do not change their values! -var SPHERICAL_HUD_DISTANCE = 1; // meters. -var PLANAR_PERPENDICULAR_HUD_DISTANCE = SPHERICAL_HUD_DISTANCE; +// This "var" is for documentation. Do not change the value! +var PLANAR_PERPENDICULAR_HUD_DISTANCE = 1; function calculateRayUICollisionPoint(position, direction) { // Answer the 3D intersection of the HUD by the given ray, or falsey if no intersection. if (HMD.active) { @@ -154,6 +202,32 @@ function overlayFromWorldPoint(point) { return { x: horizontalPixels, y: verticalPixels }; } +function activeHudPoint2d(activeHand) { // if controller is valid, update reticle position and answer 2d point. Otherwise falsey. + var controllerPose = Controller.getPoseValue(activeHand); + // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...) + if (!controllerPose.valid) { + return; // Controller is cradled. + } + var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), + MyAvatar.position); + // This gets point direction right, but if you want general quaternion it would be more complicated: + var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation)); + + var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection); + if (!hudPoint3d) { + if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here + print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong. + } + return; + } + var hudPoint2d = overlayFromWorldPoint(hudPoint3d); + + // We don't know yet if we'll want to make the cursor or laser visble, but we need to move it to see if + // it's pointing at a QML tool (aka system overlay). + setReticlePosition(hudPoint2d); + return hudPoint2d; +} + // MOUSE ACTIVITY -------- // var isSeeking = false; @@ -175,20 +249,24 @@ function isShakingMouse() { // True if the person is waving the mouse around try return isShaking; } var NON_LINEAR_DIVISOR = 2; -var MINIMUM_SEEK_DISTANCE = 0.01; -function updateSeeking() { - if (!Reticle.visible || isShakingMouse()) { - isSeeking = true; +var MINIMUM_SEEK_DISTANCE = 0.1; +function updateSeeking(doNotStartSeeking) { + if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) { + if (!isSeeking) { + print('Start seeking mouse.'); + isSeeking = true; + } } // e.g., if we're about to turn it on with first movement. if (!isSeeking) { return; } averageMouseVelocity = lastIntegration = 0; var lookAt2D = HMD.getHUDLookAtPosition2D(); - if (!lookAt2D) { + if (!lookAt2D) { // If this happens, something has gone terribly wrong. print('Cannot seek without lookAt position'); - return; - } // E.g., if parallel to location in HUD + isSeeking = false; + return; // E.g., if parallel to location in HUD + } var copy = Reticle.position; function updateDimension(axis) { var distanceBetween = lookAt2D[axis] - Reticle.position[axis]; @@ -201,6 +279,7 @@ function updateSeeking() { } var okX = !updateDimension('x'), okY = !updateDimension('y'); // Evaluate both. Don't short-circuit. if (okX && okY) { + print('Finished seeking mouse'); isSeeking = false; } else { Reticle.setPosition(copy); // Not setReticlePosition @@ -226,6 +305,11 @@ function expireMouseCursor(now) { Reticle.visible = false; } } +function hudReticleDistance() { // 3d distance from camera to the reticle position on hud + // (The camera is only in the center of the sphere on reset.) + var reticlePositionOnHUD = HMD.worldPointFromOverlay(Reticle.position); + return Vec3.distance(reticlePositionOnHUD, HMD.position); +} function onMouseMove() { // Display cursor at correct depth (as in depthReticle.js), and updateMouseActivity. if (ignoreMouseActivity()) { @@ -235,11 +319,10 @@ function onMouseMove() { if (HMD.active) { // set depth updateSeeking(); if (isPointingAtOverlay()) { - Reticle.setDepth(SPHERICAL_HUD_DISTANCE); // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE! + Reticle.depth = hudReticleDistance(); } else { var result = findRayIntersection(Camera.computePickRay(Reticle.position.x, Reticle.position.y)); - var depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH; - Reticle.setDepth(depth); + Reticle.depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH; } } updateMouseActivity(); // After the above, just in case the depth movement is awkward when becoming visible. @@ -254,206 +337,175 @@ setupHandler(Controller.mouseDoublePressEvent, onMouseClick); // CONTROLLER MAPPING --------- // +var leftTrigger = new Trigger('left'); +var rightTrigger = new Trigger('right'); +var activeTrigger = rightTrigger; var activeHand = Controller.Standard.RightHand; -function toggleHand() { +var LEFT_HUD_LASER = 1; +var RIGHT_HUD_LASER = 2; +var BOTH_HUD_LASERS = LEFT_HUD_LASER + RIGHT_HUD_LASER; +var activeHudLaser = RIGHT_HUD_LASER; +function toggleHand() { // unequivocally switch which hand controls mouse position if (activeHand === Controller.Standard.RightHand) { activeHand = Controller.Standard.LeftHand; + activeTrigger = leftTrigger; + activeHudLaser = LEFT_HUD_LASER; } else { activeHand = Controller.Standard.RightHand; + activeTrigger = rightTrigger; + activeHudLaser = RIGHT_HUD_LASER; } + clearSystemLaser(); +} +function makeToggleAction(hand) { // return a function(0|1) that makes the specified hand control mouse when 1 + return function (on) { + if (on && (activeHand !== hand)) { + toggleHand(); + } + }; } -// Create clickMappings as needed, on demand. -var clickMappings = {}, clickMapping, clickMapToggle; -var hardware; // undefined -function checkHardware() { - var newHardware = Controller.Hardware.Hydra ? 'Hydra' : (Controller.Hardware.Vive ? 'Vive' : null); // not undefined - if (hardware === newHardware) { - return; - } - print('Setting mapping for new controller hardware:', newHardware); - if (clickMapToggle) { - clickMapToggle.setState(false); - } - hardware = newHardware; - if (clickMappings[hardware]) { - clickMapping = clickMappings[hardware]; - } else { - clickMapping = Controller.newMapping(Script.resolvePath('') + '-click-' + hardware); - Script.scriptEnding.connect(clickMapping.disable); - function mapToAction(button, action) { - clickMapping.from(Controller.Hardware[hardware][button]).peek().to(Controller.Actions[action]); - } - function makeHandToggle(button, hand, optionalWhen) { - var whenThunk = optionalWhen || function () { - return true; - }; - function maybeToggle() { - if (activeHand !== Controller.Standard[hand]) { - toggleHand(); - } +var clickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); +Script.scriptEnding.connect(clickMapping.disable); - } - clickMapping.from(Controller.Hardware[hardware][button]).peek().when(whenThunk).to(maybeToggle); +// Gather the trigger data for smoothing. +clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress); +clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress); +clickMapping.from(Controller.Standard.RTClick).peek().to(rightTrigger.triggerClick); +clickMapping.from(Controller.Standard.LTClick).peek().to(leftTrigger.triggerClick); +// Full smoothed trigger is a click. +function isPointingAtOverlayStartedNonFullTrigger(trigger) { + // true if isPointingAtOverlay AND we were NOT full triggered when we became so. + // The idea is to not count clicks when we're full-triggering and reach the edge of a window. + var lockedIn = false; + return function () { + if (trigger !== activeTrigger) { + return lockedIn = false; } - function makeViveWhen(click, x, y) { - var viveClick = Controller.Hardware.Vive[click], - viveX = Controller.Standard[x], // Standard after filtering by mapping - viveY = Controller.Standard[y]; - return function () { - var clickValue = Controller.getValue(viveClick); - var xValue = Controller.getValue(viveX); - var yValue = Controller.getValue(viveY); - return clickValue && !xValue && !yValue; - }; + if (!isPointingAtOverlay()) { + return lockedIn = false; } - switch (hardware) { - case 'Hydra': - makeHandToggle('R3', 'RightHand'); - makeHandToggle('L3', 'LeftHand'); - - mapToAction('R3', 'ReticleClick'); - mapToAction('L3', 'ReticleClick'); - mapToAction('R4', 'ContextMenu'); - mapToAction('L4', 'ContextMenu'); - break; - case 'Vive': - // When touchpad click is NOT treated as movement, treat as left click - makeHandToggle('RS', 'RightHand', makeViveWhen('RS', 'RX', 'RY')); - makeHandToggle('LS', 'LeftHand', makeViveWhen('LS', 'LX', 'LY')); - clickMapping.from(Controller.Hardware.Vive.RS).when(makeViveWhen('RS', 'RX', 'RY')).to(Controller.Actions.ReticleClick); - clickMapping.from(Controller.Hardware.Vive.LS).when(makeViveWhen('LS', 'LX', 'LY')).to(Controller.Actions.ReticleClick); - mapToAction('RightApplicationMenu', 'ContextMenu'); - mapToAction('LeftApplicationMenu', 'ContextMenu'); - break; + if (lockedIn) { + return true; } - clickMappings[hardware] = clickMapping; + lockedIn = !trigger.full(); + return lockedIn; } - clickMapToggle = new LatchedToggle(clickMapping.enable, clickMapping.disable); - clickMapToggle.setState(true); } -checkHardware(); +clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(rightTrigger)).to(Controller.Actions.ReticleClick); +clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick); +// The following is essentially like Left and Right versions of +// clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); +// except that we first update the reticle position from the appropriate hand position, before invoking the ContextMenu. +var wantsMenu = 0; +clickMapping.from(function () { return wantsMenu; }).to(Controller.Actions.ContextMenu); +clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(function (clicked) { + if (clicked) { + activeHudPoint2d(Controller.Standard.RightHand); + } + wantsMenu = clicked; +}); +clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(function (clicked) { + if (clicked) { + activeHudPoint2d(Controller.Standard.LeftHand); + } + wantsMenu = clicked; +}); +clickMapping.from(Controller.Hardware.Keyboard.RightMouseClicked).peek().to(function () { + // Allow the reticle depth to be set correctly: + // Wait a tick for the context menu to be displayed, and then simulate a (non-hand-controller) mouse move + // so that the system updates qml state (Reticle.pointingAtSystemOverlay) before it gives us a mouseMove. + // We don't want the system code to always do this for us, because, e.g., we do not want to get a mouseMove + // after the Left/RightSecondaryThumb gives us a context menu. Only from the mouse. + Script.setTimeout(function () { + Reticle.setPosition(Reticle.position); + }, 0); +}); +// Partial smoothed trigger is activation. +clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand)); +clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand)); +clickMapping.enable(); // VISUAL AID ----------- // Same properties as handControllerGrab search sphere -var BALL_SIZE = 0.011; -var BALL_ALPHA = 0.5; -var fakeProjectionBall = Overlays.addOverlay("sphere", { - size: 5 * BALL_SIZE, - color: {red: 255, green: 10, blue: 10}, - ignoreRayIntersection: true, - alpha: BALL_ALPHA, - visible: false, - solid: true, - drawInFront: true // Even when burried inside of something, show it. -}); -var overlays = [fakeProjectionBall]; // If we want to try showing multiple balls and lasers. -Script.scriptEnding.connect(function () { - overlays.forEach(Overlays.deleteOverlay); -}); -var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation. -function turnOffVisualization(optionalEnableClicks) { // because we're showing cursor on HUD - if (!optionalEnableClicks) { - expireMouseCursor(); - } - if (!visualizationIsShowing) { +var LASER_ALPHA = 0.5; +var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA}; +var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA}; +var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; +var systemLaserOn = false; +function clearSystemLaser() { + if (!systemLaserOn) { return; } - visualizationIsShowing = false; - overlays.forEach(function (overlay) { - Overlays.editOverlay(overlay, {visible: false}); - }); + HMD.disableHandLasers(BOTH_HUD_LASERS); + systemLaserOn = false; + weMovedReticle = true; + Reticle.position = { x: -1, y: -1 }; } -var MAX_RAY_SCALE = 32000; // Anything large. It's a scale, not a distance. -function updateVisualization(controllerPosition, controllerDirection, hudPosition3d, hudPosition2d) { - // Show an indication of where the cursor will appear when crossing a HUD element, - // and where in-world clicking will occur. - // - // There are a number of ways we could do this, but for now, it's a blue sphere that rolls along - // the HUD surface, and a red sphere that rolls along the 3d objects that will receive the click. - // We'll leave it to other scripts (like handControllerGrab) to show a search beam when desired. - - function intersection3d(position, direction) { - // Answer in-world intersection (entity or 3d overlay), or way-out point - var pickRay = {origin: position, direction: direction}; - var result = findRayIntersection(pickRay); - return result.intersects ? result.intersection : Vec3.sum(position, Vec3.multiply(MAX_RAY_SCALE, direction)); - } - - visualizationIsShowing = true; - // We'd rather in-world interactions be done at the termination of the hand beam - // -- intersection3d(controllerPosition, controllerDirection). Maybe have handControllerGrab - // direclty manipulate both entity and 3d overlay objects. - // For now, though, we present a false projection of the cursor onto whatever is below it. This is - // different from the hand beam termination because the false projection is from the camera, while - // the hand beam termination is from the hand. - var eye = Camera.getPosition(); - var falseProjection = intersection3d(eye, Vec3.subtract(hudPosition3d, eye)); - Overlays.editOverlay(fakeProjectionBall, {visible: true, position: falseProjection}); - Reticle.visible = false; - - return visualizationIsShowing; // In case we change caller to act conditionally. +function setColoredLaser() { // answer trigger state if lasers supported, else falsey. + var color = (activeTrigger.state === 'full') ? LASER_TRIGGER_COLOR_XYZW : LASER_SEARCH_COLOR_XYZW; + return HMD.setHandLasers(activeHudLaser, true, color, SYSTEM_LASER_DIRECTION) && activeTrigger.state; } // MAIN OPERATIONS ----------- // function update() { var now = Date.now(); + function off() { + expireMouseCursor(); + clearSystemLaser(); + } + updateSeeking(true); if (!handControllerLockOut.expired(now)) { - return turnOffVisualization(); - } // Let them use mouse it in peace. + return off(); // Let them use mouse in peace. + } if (!Menu.isOptionChecked("First Person")) { - return turnOffVisualization(); - } // What to do? menus can be behind hand! - if (!Window.hasFocus()) { // Don't mess with other apps - return turnOffVisualization(); + return off(); // What to do? menus can be behind hand! } - var controllerPose = Controller.getPoseValue(activeHand); - // Vive is effectively invalid when not in HMD - if (!controllerPose.valid || ((hardware === 'Vive') && !HMD.active)) { - return turnOffVisualization(); - } // Controller is cradled. - var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), - MyAvatar.position); - // This gets point direction right, but if you want general quaternion it would be more complicated: - var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation)); - - var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection); - if (!hudPoint3d) { - print('Controller is parallel to HUD'); - return turnOffVisualization(); + if (!Window.hasFocus() || !Reticle.allowMouseCapture) { + return off(); // Don't mess with other apps or paused mouse activity + } + leftTrigger.update(); + rightTrigger.update(); + if (!activeTrigger.state) { + return off(); // No trigger + } + var hudPoint2d = activeHudPoint2d(activeHand); + if (!hudPoint2d) { + return off(); } - var hudPoint2d = overlayFromWorldPoint(hudPoint3d); - - // We don't know yet if we'll want to make the cursor visble, but we need to move it to see if - // it's pointing at a QML tool (aka system overlay). - setReticlePosition(hudPoint2d); // If there's a HUD element at the (newly moved) reticle, just make it visible and bail. if (isPointingAtOverlay(hudPoint2d)) { - if (HMD.active) { // Doesn't hurt anything without the guard, but consider it documentation. - Reticle.depth = SPHERICAL_HUD_DISTANCE; // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE! + if (HMD.active) { + Reticle.depth = hudReticleDistance(); } - Reticle.visible = true; - return turnOffVisualization(true); + if (activeTrigger.state && (!systemLaserOn || (systemLaserOn !== activeTrigger.state))) { // last=>wrong color + // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. + systemLaserOn = setColoredLaser(); + Reticle.visible = !systemLaserOn; + } else if ((systemLaserOn || Reticle.visible) && !activeTrigger.state) { + clearSystemLaser(); + Reticle.visible = false; + } + return; } // We are not pointing at a HUD element (but it could be a 3d overlay). - updateVisualization(controllerPosition, controllerDirection, hudPoint3d, hudPoint2d); + clearSystemLaser(); + Reticle.visible = false; } - -var UPDATE_INTERVAL = 20; // milliseconds. Script.update is too frequent. -var updater = Script.setInterval(update, UPDATE_INTERVAL); -Script.scriptEnding.connect(function () { - Script.clearInterval(updater); -}); +setupHandler(Script.update, update); // Check periodically for changes to setup. var SETTINGS_CHANGE_RECHECK_INTERVAL = 10 * 1000; // milliseconds function checkSettings() { updateFieldOfView(); - checkHardware(); + updateRecommendedArea(); } checkSettings(); + var settingsChecker = Script.setInterval(checkSettings, SETTINGS_CHANGE_RECHECK_INTERVAL); Script.scriptEnding.connect(function () { Script.clearInterval(settingsChecker); + OffscreenFlags.navigationFocusDisabled = false; }); + diff --git a/scripts/system/controllers/leapHands.js b/scripts/system/controllers/leapHands.js new file mode 100644 index 0000000000..1be0b1e5f6 --- /dev/null +++ b/scripts/system/controllers/leapHands.js @@ -0,0 +1,527 @@ +// +// leapHands.js +// examples +// +// Created by David Rowe on 8 Sep 2014. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script that uses the Leap Motion to make the avatar's hands replicate the user's hand actions. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var leftTriggerValue = 0; +var rightTriggerValue = 0; + +var LEAP_TRIGGER_START_ANGLE = 15.0; +var LEAP_TRIGGER_END_ANGLE = 40.0; + +function getLeapMotionLeftTrigger() { + //print("left trigger = " + leftTriggerValue); + return leftTriggerValue; +} +function getLeapMotionRightTrigger() { + //print("right trigger = " + rightTriggerValue); + return rightTriggerValue; +} + +var leapHands = (function () { + + var isOnHMD, + LEAP_ON_HMD_MENU_ITEM = "Leap Motion on HMD", + LEAP_OFFSET = 0.019, // Thickness of Leap Motion plus HMD clip + HMD_OFFSET = 0.070, // Eyeballs to front surface of Oculus DK2 TODO: Confirm and make depend on device and eye relief + 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 + fingers, + NUM_FINGERS = 5, // 0 = thumb; ...; 4 = pinky + THUMB = 0, + MIDDLE_FINGER = 2, + NUM_FINGER_JOINTS = 3, // 0 = metacarpal(hand)-proximal(finger) joint; ...; 2 = intermediate-distal joint + MAX_HAND_INACTIVE_COUNT = 20, + calibrationStatus, + UNCALIBRATED = 0, + CALIBRATING = 1, + CALIBRATED = 2, + CALIBRATION_TIME = 1000, // milliseconds + avatarScale, + avatarFaceModelURL, + avatarSkeletonModelURL, + 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, + i; + + print(MyAvatar.skeletonModelURL); + + print("Skeleton joint names ..."); + jointNames = MyAvatar.getJointNames(); + for (i = 0; i < jointNames.length; i += 1) { + print(i + ": " + jointNames[i]); + } + print("... skeleton joint names"); + } + + 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, + middleFingerPosition, + leapHandHeight, + h; + + if (!isOnHMD) { + if (hands[0].controller.isActive() && hands[1].controller.isActive()) { + leapHandHeight = (hands[0].controller.getAbsTranslation().y + hands[1].controller.getAbsTranslation().y) / 2.0; + } else { + calibrationStatus = UNCALIBRATED; + return; + } + } + + avatarPosition = MyAvatar.position; + + for (h = 0; h < NUM_HANDS; h += 1) { + handPosition = MyAvatar.getJointPosition(hands[h].jointName); + if (!hasHandAndWristJoints) { + middleFingerPosition = MyAvatar.getJointPosition(fingers[h][MIDDLE_FINGER][0].jointName); + handToWristOffset[h] = Vec3.multiply(Vec3.subtract(handPosition, middleFingerPosition), 1.0 - HAND_OFFSET); + } + + if (isOnHMD) { + // Offset of Leap Motion origin from physical eye position + hands[h].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET }; + } else { + hands[h].zeroPosition = { + x: handPosition.x - avatarPosition.x, + y: handPosition.y - avatarPosition.y, + z: avatarPosition.z - handPosition.z + }; + hands[h].zeroPosition = Vec3.multiplyQbyV(MyAvatar.orientation, hands[h].zeroPosition); + hands[h].zeroPosition.y = hands[h].zeroPosition.y - leapHandHeight; + } + } + + MyAvatar.clearJointData("LeftHand"); + MyAvatar.clearJointData("LeftForeArm"); + MyAvatar.clearJointData("RightHand"); + MyAvatar.clearJointData("RightForeArm"); + + calibrationStatus = CALIBRATED; + print("Leap Motion: Calibrated"); + } + + function calibrate() { + var jointNames, + i; + + calibrationStatus = CALIBRATING; + + avatarScale = MyAvatar.scale; + avatarFaceModelURL = MyAvatar.faceModelURL; + avatarSkeletonModelURL = MyAvatar.skeletonModelURL; + + // Does this skeleton have both wrist and hand joints? + hasHandAndWristJoints = false; + jointNames = MyAvatar.getJointNames(); + for (i = 0; i < jointNames.length; i += 1) { + hasHandAndWristJoints = hasHandAndWristJoints || jointNames[i].toLowerCase() === "leftwrist"; + } + + // Set avatar arms vertical, forearms horizontal, as "zero" position for calibration + 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); + } + + function checkCalibration() { + + if (calibrationStatus === CALIBRATED) { + return true; + } + + if (calibrationStatus !== CALIBRATING) { + calibrate(); + } + + return false; + } + + function setIsOnHMD() { + isOnHMD = Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM); + print("Leap Motion: " + (isOnHMD ? "Is on HMD" : "Is on desk")); + } + + function checkSettings() { + if (calibrationStatus > UNCALIBRATED && (MyAvatar.scale !== avatarScale + || MyAvatar.faceModelURL !== avatarFaceModelURL + || MyAvatar.skeletonModelURL !== avatarSkeletonModelURL + || Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM) !== isOnHMD)) { + print("Leap Motion: Recalibrate..."); + calibrationStatus = UNCALIBRATED; + + setIsOnHMD(); + } + } + + function setUp() { + + wrists = [ + { + jointName: "LeftWrist", + controller: Controller.createInputController("Spatial", "joint_L_wrist") + }, + { + jointName: "RightWrist", + controller: Controller.createInputController("Spatial", "joint_R_wrist") + } + ]; + + hands = [ + { + jointName: "LeftHand", + controller: Controller.createInputController("Spatial", "joint_L_hand"), + inactiveCount: 0 + }, + { + jointName: "RightHand", + controller: Controller.createInputController("Spatial", "joint_R_hand"), + inactiveCount: 0 + } + ]; + + // The Leap controller's first joint is the hand-metacarpal joint but this joint's data is not used because it's too + // dependent on the model skeleton exactly matching the Leap skeleton; using just the second and subsequent joints + // seems to work better over all. + fingers = [{}, {}]; + fingers[0] = [ + [ + { jointName: "LeftHandThumb1", controller: Controller.createInputController("Spatial", "joint_L_thumb2") }, + { jointName: "LeftHandThumb2", controller: Controller.createInputController("Spatial", "joint_L_thumb3") }, + { jointName: "LeftHandThumb3", controller: Controller.createInputController("Spatial", "joint_L_thumb4") } + ], + [ + { jointName: "LeftHandIndex1", controller: Controller.createInputController("Spatial", "joint_L_index2") }, + { jointName: "LeftHandIndex2", controller: Controller.createInputController("Spatial", "joint_L_index3") }, + { jointName: "LeftHandIndex3", controller: Controller.createInputController("Spatial", "joint_L_index4") } + ], + [ + { jointName: "LeftHandMiddle1", controller: Controller.createInputController("Spatial", "joint_L_middle2") }, + { jointName: "LeftHandMiddle2", controller: Controller.createInputController("Spatial", "joint_L_middle3") }, + { jointName: "LeftHandMiddle3", controller: Controller.createInputController("Spatial", "joint_L_middle4") } + ], + [ + { jointName: "LeftHandRing1", controller: Controller.createInputController("Spatial", "joint_L_ring2") }, + { jointName: "LeftHandRing2", controller: Controller.createInputController("Spatial", "joint_L_ring3") }, + { jointName: "LeftHandRing3", controller: Controller.createInputController("Spatial", "joint_L_ring4") } + ], + [ + { jointName: "LeftHandPinky1", controller: Controller.createInputController("Spatial", "joint_L_pinky2") }, + { jointName: "LeftHandPinky2", controller: Controller.createInputController("Spatial", "joint_L_pinky3") }, + { jointName: "LeftHandPinky3", controller: Controller.createInputController("Spatial", "joint_L_pinky4") } + ] + ]; + fingers[1] = [ + [ + { jointName: "RightHandThumb1", controller: Controller.createInputController("Spatial", "joint_R_thumb2") }, + { jointName: "RightHandThumb2", controller: Controller.createInputController("Spatial", "joint_R_thumb3") }, + { jointName: "RightHandThumb3", controller: Controller.createInputController("Spatial", "joint_R_thumb4") } + ], + [ + { jointName: "RightHandIndex1", controller: Controller.createInputController("Spatial", "joint_R_index2") }, + { jointName: "RightHandIndex2", controller: Controller.createInputController("Spatial", "joint_R_index3") }, + { jointName: "RightHandIndex3", controller: Controller.createInputController("Spatial", "joint_R_index4") } + ], + [ + { jointName: "RightHandMiddle1", controller: Controller.createInputController("Spatial", "joint_R_middle2") }, + { jointName: "RightHandMiddle2", controller: Controller.createInputController("Spatial", "joint_R_middle3") }, + { jointName: "RightHandMiddle3", controller: Controller.createInputController("Spatial", "joint_R_middle4") } + ], + [ + { jointName: "RightHandRing1", controller: Controller.createInputController("Spatial", "joint_R_ring2") }, + { jointName: "RightHandRing2", controller: Controller.createInputController("Spatial", "joint_R_ring3") }, + { jointName: "RightHandRing3", controller: Controller.createInputController("Spatial", "joint_R_ring4") } + ], + [ + { jointName: "RightHandPinky1", controller: Controller.createInputController("Spatial", "joint_R_pinky2") }, + { jointName: "RightHandPinky2", controller: Controller.createInputController("Spatial", "joint_R_pinky3") }, + { jointName: "RightHandPinky3", controller: Controller.createInputController("Spatial", "joint_R_pinky4") } + ] + ]; + + handAnimationStateHandlers = [null, null]; + handAnimationStateFunctions = [animateLeftHand, animateRightHand]; + handAnimationStateProperties = [ + ["leftHandType", "leftHandPosition", "leftHandRotation"], + ["rightHandType", "rightHandPosition", "rightHandPosition"] + ]; + + setIsOnHMD(); + + settingsTimer = Script.setInterval(checkSettings, 2000); + + calibrationStatus = UNCALIBRATED; + + { + var mapping = Controller.newMapping("LeapmotionTrigger"); + mapping.from(getLeapMotionLeftTrigger).to(Controller.Standard.LT); + mapping.from(getLeapMotionRightTrigger).to(Controller.Standard.RT); + mapping.enable(); + } + } + + function moveHands() { + var h, + i, + j, + side, + handOffset, + wristOffset, + handRotation, + locRotation, + cameraOrientation, + inverseAvatarOrientation; + + for (h = 0; h < NUM_HANDS; h += 1) { + side = h === 0 ? -1.0 : 1.0; + + if (hands[h].controller.isActive()) { + + // Calibrate if necessary. + if (!checkCalibration()) { + 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(); + + if (isOnHMD) { + + // Adjust to control wrist position if "hand" joint is at wrist ... + if (!hasHandAndWristJoints) { + wristOffset = Vec3.multiplyQbyV(handRotation, handToWristOffset[h]); + handOffset = Vec3.sum(handOffset, wristOffset); + } + + // Hand offset in camera coordinates ... + handOffset = { + x: -handOffset.x, + y: -handOffset.z, + z: -handOffset.y - hands[h].zeroPosition.z + }; + + // Hand offset in world coordinates ... + cameraOrientation = Camera.getOrientation(); + handOffset = Vec3.sum(Camera.getPosition(), Vec3.multiplyQbyV(cameraOrientation, handOffset)); + + // 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.y, + y: -handRotation.z, + z: -handRotation.x, + w: handRotation.w + }; + + // Hand rotation in avatar coordinates ... + 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 { + + // Adjust to control wrist position if "hand" joint is at wrist ... + if (!hasHandAndWristJoints) { + wristOffset = Vec3.multiplyQbyV(handRotation, handToWristOffset[h]); + handOffset = Vec3.sum(handOffset, wristOffset); + } + + // Hand offset in camera coordinates ... + handOffset = { + x: -handOffset.x, + y: hands[h].zeroPosition.y + handOffset.y, + z: hands[h].zeroPosition.z - handOffset.z + }; + + // Hand rotation in camera coordinates ... + handRotation = { + x: handRotation.z, + y: handRotation.y, + z: handRotation.x, + w: handRotation.w + }; + + // Hand rotation in avatar coordinates ... + handRotation = Quat.multiply(DESKTOP_CAMERA_TO_AVATAR_ROTATION, handRotation); + } + + // Set hand position and orientation for animation state handler ... + hands[h].position = handOffset; + hands[h].rotation = handRotation; + + // Set finger joints ... + var summed = 0; + var closeAngle = 0; + for (i = 0; i < NUM_FINGERS; i += 1) { + for (j = 0; j < NUM_FINGER_JOINTS; j += 1) { + if (fingers[h][i][j].controller !== null) { + locRotation = fingers[h][i][j].controller.getLocRotation(); + var eulers = Quat.safeEulerAngles(locRotation); + closeAngle += eulers.x; + + summed++; + + if (i === THUMB) { + locRotation = { + x: side * locRotation.y, + y: side * -locRotation.z, + 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, + y: -locRotation.z, + z: -locRotation.y, + w: locRotation.w + }; + } + MyAvatar.setJointRotation(fingers[h][i][j].jointName, locRotation); + } + } + } + + hands[h].inactiveCount = 0; + if (summed > 0) { + closeAngle /= summed; + } + + var triggerValue = (-closeAngle - LEAP_TRIGGER_START_ANGLE) / (LEAP_TRIGGER_END_ANGLE - LEAP_TRIGGER_START_ANGLE); + triggerValue = Math.max(0.0, Math.min(triggerValue, 1.0)); + + if (h == 0) { + leftTriggerValue = triggerValue; + } else { + rightTriggerValue = triggerValue; + + } + + } else { + + if (hands[h].inactiveCount < MAX_HAND_INACTIVE_COUNT) { + + hands[h].inactiveCount += 1; + + if (hands[h].inactiveCount === MAX_HAND_INACTIVE_COUNT) { + if (handAnimationStateHandlers[h] !== null) { + MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]); + handAnimationStateHandlers[h] = null; + leftTriggerValue = 0.0; + rightTriggerValue = 0.0; + } + } + } + } + } + } + + function tearDown() { + var h, + i, + j; + + Script.clearInterval(settingsTimer); + + 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) { + Controller.releaseInputController(fingers[h][i][j].controller); + } + } + } + } + } + + return { + printSkeletonJointNames: printSkeletonJointNames, + setUp : setUp, + moveHands : moveHands, + tearDown : tearDown + }; +}()); + + +//leapHands.printSkeletonJointNames(); + +leapHands.setUp(); +Script.update.connect(leapHands.moveHands); +Script.scriptEnding.connect(leapHands.tearDown); diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js new file mode 100644 index 0000000000..3d40bfb9eb --- /dev/null +++ b/scripts/system/controllers/teleport.js @@ -0,0 +1,625 @@ +// Created by james b. pollack @imgntn on 7/2/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Creates a beam and target and then teleports you there when you let go of either activation button. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var inTeleportMode = false; + +var currentFadeSphereOpacity = 1; +var fadeSphereInterval = null; +var fadeSphereUpdateInterval = null; +//milliseconds between fading one-tenth -- so this is a half second fade total +var USE_FADE_MODE = false; +var USE_FADE_OUT = true; +var FADE_OUT_INTERVAL = 25; + +// instant +// var NUMBER_OF_STEPS = 0; +// var SMOOTH_ARRIVAL_SPACING = 0; + +// // slow +// var SMOOTH_ARRIVAL_SPACING = 150; +// var NUMBER_OF_STEPS = 2; + +// medium-slow +// var SMOOTH_ARRIVAL_SPACING = 100; +// var NUMBER_OF_STEPS = 4; + +// medium-fast +var SMOOTH_ARRIVAL_SPACING = 33; +var NUMBER_OF_STEPS = 6; + +//fast +// var SMOOTH_ARRIVAL_SPACING = 10; +// var NUMBER_OF_STEPS = 20; + + +var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport.fbx"); +var TARGET_MODEL_DIMENSIONS = { + x: 1.15, + y: 0.5, + z: 1.15 + +}; + +function ThumbPad(hand) { + this.hand = hand; + var _thisPad = this; + + this.buttonPress = function(value) { + _thisPad.buttonValue = value; + }; + +} + +function Trigger(hand) { + this.hand = hand; + var _this = this; + + this.buttonPress = function(value) { + _this.buttonValue = value; + + }; + + this.down = function() { + var down = _this.buttonValue === 1 ? 1.0 : 0.0; + return down + }; +} + +function Teleporter() { + var _this = this; + this.intersection = null; + this.rightOverlayLine = null; + this.leftOverlayLine = null; + this.targetOverlay = null; + this.updateConnected = null; + this.smoothArrivalInterval = null; + this.fadeSphere = null; + this.teleportHand = null; + + this.initialize = function() { + this.createMappings(); + this.disableGrab(); + }; + + this.createTargetOverlay = function() { + + if (_this.targetOverlay !== null) { + return; + } + var targetOverlayProps = { + url: TARGET_MODEL_URL, + dimensions: TARGET_MODEL_DIMENSIONS, + visible: true + }; + + _this.targetOverlay = Overlays.addOverlay("model", targetOverlayProps); + }; + + this.createMappings = function() { + teleporter.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random(); + teleporter.teleportMappingInternal = Controller.newMapping(teleporter.telporterMappingInternalName); + + Controller.enableMapping(teleporter.telporterMappingInternalName); + }; + + this.disableMappings = function() { + Controller.disableMapping(teleporter.telporterMappingInternalName); + }; + + this.enterTeleportMode = function(hand) { + if (inTeleportMode === true) { + return; + } + inTeleportMode = true; + if (this.smoothArrivalInterval !== null) { + Script.clearInterval(this.smoothArrivalInterval); + } + if (fadeSphereInterval !== null) { + Script.clearInterval(fadeSphereInterval); + } + this.teleportHand = hand; + this.initialize(); + Script.update.connect(this.update); + this.updateConnected = true; + }; + + this.createFadeSphere = function(avatarHead) { + var sphereProps = { + position: avatarHead, + size: -1, + color: { + red: 0, + green: 0, + blue: 0, + }, + alpha: 1, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }; + + currentFadeSphereOpacity = 10; + + _this.fadeSphere = Overlays.addOverlay("sphere", sphereProps); + Script.clearInterval(fadeSphereInterval) + Script.update.connect(_this.updateFadeSphere); + if (USE_FADE_OUT === true) { + this.fadeSphereOut(); + } + + + }; + + this.fadeSphereOut = function() { + + fadeSphereInterval = Script.setInterval(function() { + if (currentFadeSphereOpacity <= 0) { + Script.clearInterval(fadeSphereInterval); + _this.deleteFadeSphere(); + fadeSphereInterval = null; + return; + } + if (currentFadeSphereOpacity > 0) { + currentFadeSphereOpacity = currentFadeSphereOpacity - 1; + } + + Overlays.editOverlay(_this.fadeSphere, { + alpha: currentFadeSphereOpacity / 10 + }) + + }, FADE_OUT_INTERVAL); + }; + + + this.updateFadeSphere = function() { + var headPosition = MyAvatar.getHeadPosition(); + Overlays.editOverlay(_this.fadeSphere, { + position: headPosition + }) + }; + + this.deleteFadeSphere = function() { + if (_this.fadeSphere !== null) { + Script.update.disconnect(_this.updateFadeSphere); + Overlays.deleteOverlay(_this.fadeSphere); + _this.fadeSphere = null; + } + + }; + + this.deleteTargetOverlay = function() { + if (this.targetOverlay === null) { + return; + } + Overlays.deleteOverlay(this.targetOverlay); + this.intersection = null; + this.targetOverlay = null; + } + + this.turnOffOverlayBeams = function() { + this.rightOverlayOff(); + this.leftOverlayOff(); + } + + this.exitTeleportMode = function(value) { + if (this.updateConnected === true) { + Script.update.disconnect(this.update); + } + this.disableMappings(); + this.turnOffOverlayBeams(); + + + this.updateConnected = null; + + Script.setTimeout(function() { + inTeleportMode = false; + _this.enableGrab(); + }, 100); + }; + + + + this.update = function() { + + if (teleporter.teleportHand === 'left') { + teleporter.leftRay(); + + if ((leftPad.buttonValue === 0 || leftTrigger.buttonValue === 0) && inTeleportMode === true) { + _this.teleport(); + return; + } + + } else { + teleporter.rightRay(); + + if ((rightPad.buttonValue === 0 || rightTrigger.buttonValue === 0) && inTeleportMode === true) { + _this.teleport(); + return; + } + } + + }; + + this.rightRay = function() { + + + var rightPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getPoseValue(Controller.Standard.RightHand).translation), MyAvatar.position); + + var rightControllerRotation = Controller.getPoseValue(Controller.Standard.RightHand).rotation; + + var rightRotation = Quat.multiply(MyAvatar.orientation, rightControllerRotation) + + + var rightFinal = Quat.multiply(rightRotation, Quat.angleAxis(90, { + x: 1, + y: 0, + z: 0 + })); + + var rightPickRay = { + origin: rightPosition, + direction: Quat.getUp(rightRotation), + }; + + this.rightPickRay = rightPickRay; + + var location = Vec3.sum(rightPickRay.origin, Vec3.multiply(rightPickRay.direction, 500)); + + + var rightIntersection = Entities.findRayIntersection(teleporter.rightPickRay, true, [], [this.targetEntity]); + + if (rightIntersection.intersects) { + this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, { + red: 7, + green: 36, + blue: 44 + }); + if (this.targetOverlay !== null) { + this.updateTargetOverlay(rightIntersection); + } else { + this.createTargetOverlay(); + } + + } else { + + this.deleteTargetOverlay(); + this.rightLineOn(rightPickRay.origin, location, { + red: 7, + green: 36, + blue: 44 + }); + } + } + + + this.leftRay = function() { + var leftPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getPoseValue(Controller.Standard.LeftHand).translation), MyAvatar.position); + + var leftRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(Controller.Standard.LeftHand).rotation) + + + var leftFinal = Quat.multiply(leftRotation, Quat.angleAxis(90, { + x: 1, + y: 0, + z: 0 + })); + + + var leftPickRay = { + origin: leftPosition, + direction: Quat.getUp(leftRotation), + }; + + this.leftPickRay = leftPickRay; + + var location = Vec3.sum(MyAvatar.position, Vec3.multiply(leftPickRay.direction, 500)); + + + var leftIntersection = Entities.findRayIntersection(teleporter.leftPickRay, true, [], [this.targetEntity]); + + if (leftIntersection.intersects) { + + this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, { + red: 7, + green: 36, + blue: 44 + }); + if (this.targetOverlay !== null) { + this.updateTargetOverlay(leftIntersection); + } else { + this.createTargetOverlay(); + } + + + } else { + + + this.deleteTargetOverlay(); + this.leftLineOn(leftPickRay.origin, location, { + red: 7, + green: 36, + blue: 44 + }); + } + }; + + this.rightLineOn = function(closePoint, farPoint, color) { + if (this.rightOverlayLine === null) { + var lineProperties = { + start: closePoint, + end: farPoint, + color: color, + ignoreRayIntersection: true, // always ignore this + visible: true, + alpha: 1, + solid: true, + drawInFront: true, + glow: 1.0 + }; + + this.rightOverlayLine = Overlays.addOverlay("line3d", lineProperties); + + } else { + var success = Overlays.editOverlay(this.rightOverlayLine, { + lineWidth: 50, + start: closePoint, + end: farPoint, + color: color, + visible: true, + ignoreRayIntersection: true, // always ignore this + alpha: 1, + glow: 1.0 + }); + } + }; + + this.leftLineOn = function(closePoint, farPoint, color) { + if (this.leftOverlayLine === null) { + var lineProperties = { + ignoreRayIntersection: true, // always ignore this + start: closePoint, + end: farPoint, + color: color, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + drawInFront: true + }; + + this.leftOverlayLine = Overlays.addOverlay("line3d", lineProperties); + + } else { + var success = Overlays.editOverlay(this.leftOverlayLine, { + start: closePoint, + end: farPoint, + color: color, + visible: true, + alpha: 1, + solid: true, + glow: 1.0 + }); + } + }; + this.rightOverlayOff = function() { + if (this.rightOverlayLine !== null) { + Overlays.deleteOverlay(this.rightOverlayLine); + this.rightOverlayLine = null; + } + }; + + this.leftOverlayOff = function() { + if (this.leftOverlayLine !== null) { + Overlays.deleteOverlay(this.leftOverlayLine); + this.leftOverlayLine = null; + } + }; + + this.updateTargetOverlay = function(intersection) { + _this.intersection = intersection; + + var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP) + var euler = Quat.safeEulerAngles(rotation) + var position = { + x: intersection.intersection.x, + y: intersection.intersection.y + TARGET_MODEL_DIMENSIONS.y / 2, + z: intersection.intersection.z + } + Overlays.editOverlay(this.targetOverlay, { + position: position, + rotation: Quat.fromPitchYawRollDegrees(0, euler.y, 0), + }); + + }; + + this.disableGrab = function() { + Messages.sendLocalMessage('Hifi-Hand-Disabler', this.teleportHand); + }; + + this.enableGrab = function() { + Messages.sendLocalMessage('Hifi-Hand-Disabler', 'none'); + }; + + this.triggerHaptics = function() { + var hand = this.teleportHand === 'left' ? 0 : 1; + var haptic = Controller.triggerShortHapticPulse(0.2, hand); + }; + + this.teleport = function(value) { + if (value === undefined) { + this.exitTeleportMode(); + } + if (this.intersection !== null) { + if (USE_FADE_MODE === true) { + this.createFadeSphere(); + } + var offset = getAvatarFootOffset(); + this.intersection.intersection.y += offset; + this.exitTeleportMode(); + this.smoothArrival(); + + } + + }; + + + this.findMidpoint = function(start, end) { + var xy = Vec3.sum(start, end); + var midpoint = Vec3.multiply(0.5, xy); + return midpoint + }; + + + + this.getArrivalPoints = function(startPoint, endPoint) { + var arrivalPoints = []; + + + var i; + var lastPoint; + + for (i = 0; i < NUMBER_OF_STEPS; i++) { + if (i === 0) { + lastPoint = startPoint; + } + var newPoint = _this.findMidpoint(lastPoint, endPoint); + lastPoint = newPoint; + arrivalPoints.push(newPoint); + } + + arrivalPoints.push(endPoint) + + return arrivalPoints + }; + + this.smoothArrival = function() { + + _this.arrivalPoints = _this.getArrivalPoints(MyAvatar.position, _this.intersection.intersection); + _this.smoothArrivalInterval = Script.setInterval(function() { + if (_this.arrivalPoints.length === 0) { + Script.clearInterval(_this.smoothArrivalInterval); + return; + } + + var landingPoint = _this.arrivalPoints.shift(); + MyAvatar.position = landingPoint; + + if (_this.arrivalPoints.length === 1 || _this.arrivalPoints.length === 0) { + _this.deleteTargetOverlay(); + } + + + }, SMOOTH_ARRIVAL_SPACING) + } +} + + +//related to repositioning the avatar after you teleport +function getAvatarFootOffset() { + var data = getJointData(); + var upperLeg, lowerLeg, foot, toe, toeTop; + data.forEach(function(d) { + + var jointName = d.joint; + if (jointName === "RightUpLeg") { + upperLeg = d.translation.y; + } + if (jointName === "RightLeg") { + lowerLeg = d.translation.y; + } + if (jointName === "RightFoot") { + foot = d.translation.y; + } + if (jointName === "RightToeBase") { + toe = d.translation.y; + } + if (jointName === "RightToe_End") { + toeTop = d.translation.y + } + }) + + var myPosition = MyAvatar.position; + var offset = upperLeg + lowerLeg + foot + toe + toeTop; + offset = offset / 100; + return offset +}; + +function getJointData() { + var allJointData = []; + var jointNames = MyAvatar.jointNames; + jointNames.forEach(function(joint, index) { + var translation = MyAvatar.getJointTranslation(index); + var rotation = MyAvatar.getJointRotation(index) + allJointData.push({ + joint: joint, + index: index, + translation: translation, + rotation: rotation + }); + }); + + return allJointData; +}; + +var leftPad = new ThumbPad('left'); +var rightPad = new ThumbPad('right'); +var leftTrigger = new Trigger('left'); +var rightTrigger = new Trigger('right'); + +var mappingName, teleportMapping; + +var TELEPORT_DELAY = 100; + + +function registerMappings() { + mappingName = 'Hifi-Teleporter-Dev-' + Math.random(); + teleportMapping = Controller.newMapping(mappingName); + teleportMapping.from(Controller.Standard.RT).peek().to(rightTrigger.buttonPress); + teleportMapping.from(Controller.Standard.LT).peek().to(leftTrigger.buttonPress); + + teleportMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(rightPad.buttonPress); + teleportMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(leftPad.buttonPress); + + teleportMapping.from(Controller.Standard.LeftPrimaryThumb).when(leftTrigger.down).to(function(value) { + teleporter.enterTeleportMode('left') + return; + }); + teleportMapping.from(Controller.Standard.RightPrimaryThumb).when(rightTrigger.down).to(function(value) { + teleporter.enterTeleportMode('right') + return; + }); + teleportMapping.from(Controller.Standard.RT).when(Controller.Standard.RightPrimaryThumb).to(function(value) { + teleporter.enterTeleportMode('right') + return; + }); + teleportMapping.from(Controller.Standard.LT).when(Controller.Standard.LeftPrimaryThumb).to(function(value) { + teleporter.enterTeleportMode('left') + return; + }); + +} + +registerMappings(); + +var teleporter = new Teleporter(); + +Controller.enableMapping(mappingName); + +Script.scriptEnding.connect(cleanup); + +function cleanup() { + teleportMapping.disable(); + teleporter.disableMappings(); + teleporter.deleteTargetOverlay(); + teleporter.turnOffOverlayBeams(); + teleporter.deleteFadeSphere(); + if (teleporter.updateConnected !== null) { + Script.update.disconnect(teleporter.update); + } +} diff --git a/scripts/system/edit.js b/scripts/system/edit.js index e84cdf7971..f7e44933d7 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -12,11 +12,13 @@ // HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; - +var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; +var SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; +var EDIT_TOOLBAR = "com.highfidelity.interface.toolbar.edit"; + Script.include([ "libraries/stringHelpers.js", "libraries/dataViewHelpers.js", - "libraries/toolBars.js", "libraries/progressDialog.js", "libraries/entitySelectionTool.js", @@ -50,11 +52,6 @@ selectionManager.addEventListener(function() { lightOverlayManager.updatePositions(); }); -var toolIconUrl = Script.resolvePath("assets/images/tools/"); -var toolHeight = 50; -var toolWidth = 50; -var TOOLBAR_MARGIN_Y = 0; - var DEGREES_TO_RADIANS = Math.PI / 180.0; var RADIANS_TO_DEGREES = 180.0 / Math.PI; var epsilon = 0.001; @@ -98,12 +95,11 @@ var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary p var mode = 0; var isActive = false; -var placingEntityID = null; - IMPORTING_SVO_OVERLAY_WIDTH = 144; IMPORTING_SVO_OVERLAY_HEIGHT = 30; IMPORTING_SVO_OVERLAY_MARGIN = 5; IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34; + var importingSVOImageOverlay = Overlays.addOverlay("image", { imageURL: Script.resolvePath("assets") + "/images/hourglass.svg", width: 20, @@ -150,6 +146,8 @@ function showMarketplace(marketplaceID) { marketplaceWindow.setURL(url); marketplaceWindow.setVisible(true); marketplaceWindow.raise(); + + UserActivityLogger.logAction("opened_marketplace"); } function hideMarketplace() { @@ -166,233 +164,15 @@ function toggleMarketplace() { } var toolBar = (function() { + var EDIT_SETTING = "io.highfidelity.isEditting"; // for communication with other scripts + var TOOL_ICON_URL = Script.resolvePath("assets/images/tools/"); var that = {}, toolBar, - activeButton, - newModelButton, - newCubeButton, - newSphereButton, - newLightButton, - newTextButton, - newWebButton, - newZoneButton, - newParticleButton - - function initialize() { - toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.edit.toolbar", function(windowDimensions, toolbar) { - return { - x: windowDimensions.x / 2, - y: windowDimensions.y - }; - }, { - x: toolWidth, - y: -TOOLBAR_MARGIN_Y - toolHeight - }); - - activeButton = toolBar.addTool({ - imageURL: toolIconUrl + "edit-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - visible: true - }, true, false); - - newModelButton = toolBar.addTool({ - imageURL: toolIconUrl + "model-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newCubeButton = toolBar.addTool({ - imageURL: toolIconUrl + "cube-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newSphereButton = toolBar.addTool({ - imageURL: toolIconUrl + "sphere-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newLightButton = toolBar.addTool({ - imageURL: toolIconUrl + "light-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newTextButton = toolBar.addTool({ - imageURL: toolIconUrl + "text-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newWebButton = toolBar.addTool({ - imageURL: toolIconUrl + "web-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newZoneButton = toolBar.addTool({ - imageURL: toolIconUrl + "zone-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newParticleButton = toolBar.addTool({ - imageURL: toolIconUrl + "particle-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - that.setActive(false); - } - - that.clearEntityList = function() { - entityListTool.clearEntityList(); - }; - - that.setActive = function(active) { - if (active != isActive) { - if (active && !Entities.canAdjustLocks()) { - Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG); - } else { - Messages.sendLocalMessage("edit-events", JSON.stringify({ - enabled: active - })); - isActive = active; - if (!isActive) { - entityListTool.setVisible(false); - gridTool.setVisible(false); - grid.setEnabled(false); - propertiesTool.setVisible(false); - selectionManager.clearSelections(); - cameraManager.disable(); - } else { - hasShownPropertiesTool = false; - entityListTool.setVisible(true); - gridTool.setVisible(true); - grid.setEnabled(true); - propertiesTool.setVisible(true); - Window.setFocus(); - } - that.showTools(isActive); - } - } - toolBar.selectTool(activeButton, isActive); - lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); - Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); - }; - - // Sets visibility of tool buttons, excluding the power button - that.showTools = function(doShow) { - toolBar.showTool(newModelButton, doShow); - toolBar.showTool(newCubeButton, doShow); - toolBar.showTool(newSphereButton, doShow); - toolBar.showTool(newLightButton, doShow); - toolBar.showTool(newTextButton, doShow); - toolBar.showTool(newWebButton, doShow); - toolBar.showTool(newZoneButton, doShow); - toolBar.showTool(newParticleButton, doShow); - }; - - var RESIZE_INTERVAL = 50; - var RESIZE_TIMEOUT = 120000; // 2 minutes - var RESIZE_MAX_CHECKS = RESIZE_TIMEOUT / RESIZE_INTERVAL; - - function addModel(url) { - var entityID = createNewEntity({ - type: "Model", - modelURL: url - }, false); - - if (entityID) { - print("Model added: " + url); - selectionManager.setSelections([entityID]); - } - } - - function createNewEntity(properties, dragOnCreate) { - // Default to true if not passed in - dragOnCreate = dragOnCreate == undefined ? true : dragOnCreate; + systemToolbar, + activeButton; + + function createNewEntity(properties) { + Settings.setValue(EDIT_SETTING, false); var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS; var position = getPositionToCreateEntity(); @@ -400,11 +180,7 @@ var toolBar = (function() { if (position != null) { position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions), properties.position = position; - entityID = Entities.addEntity(properties); - if (dragOnCreate) { - placingEntityID = entityID; - } } else { Window.alert("Can't create " + properties.type + ": " + properties.type + " would be out of bounds."); } @@ -416,35 +192,74 @@ var toolBar = (function() { return entityID; } - that.mousePressEvent = function(event) { - var clickedOverlay, - url, - file; - - if (!event.isLeftButton) { - // if another mouse button than left is pressed ignore it - return false; + function cleanup() { + that.setActive(false); + systemToolbar.removeButton(EDIT_TOGGLE_BUTTON); + } + + function addButton(name, image, handler) { + var imageUrl = TOOL_ICON_URL + image; + var button = toolBar.addButton({ + objectName: name, + imageURL: imageUrl, + buttonState: 1, + alpha: 0.9, + visible: true, + }); + if (handler) { + button.clicked.connect(function() { + Script.setTimeout(handler, 100); + }); } + return button; + } - clickedOverlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y + function initialize() { + print("QQQ creating edit toolbar"); + Script.scriptEnding.connect(cleanup); + + Window.domainChanged.connect(function() { + that.setActive(false); + that.clearEntityList(); }); - if (activeButton === toolBar.clicked(clickedOverlay)) { - that.setActive(!isActive); - return true; - } - - if (newModelButton === toolBar.clicked(clickedOverlay)) { - url = Window.prompt("Model URL"); - if (url !== null && url !== "") { - addModel(url); + Entities.canAdjustLocksChanged.connect(function(canAdjustLocks) { + if (isActive && !canAdjustLocks) { + that.setActive(false); } - return true; - } + }); - if (newCubeButton === toolBar.clicked(clickedOverlay)) { + systemToolbar = Toolbars.getToolbar(SYSTEM_TOOLBAR); + activeButton = systemToolbar.addButton({ + objectName: EDIT_TOGGLE_BUTTON, + imageURL: TOOL_ICON_URL + "edit.svg", + visible: true, + alpha: 0.9, + buttonState: 1, + hoverState: 3, + defaultState: 1, + }); + activeButton.clicked.connect(function() { + that.setActive(!isActive); + activeButton.writeProperty("buttonState", isActive ? 0 : 1); + activeButton.writeProperty("defaultState", isActive ? 0 : 1); + activeButton.writeProperty("hoverState", isActive ? 2 : 3); + }); + + toolBar = Toolbars.getToolbar(EDIT_TOOLBAR); + toolBar.writeProperty("shown", false); + + addButton("newModelButton", "model-01.svg", function() { + var url = Window.prompt("Model URL"); + if (url !== null && url !== "") { + createNewEntity({ + type: "Model", + modelURL: url + }); + } + }); + + addButton("newCubeButton", "cube-01.svg", function() { createNewEntity({ type: "Box", dimensions: DEFAULT_DIMENSIONS, @@ -454,11 +269,9 @@ var toolBar = (function() { blue: 0 } }); + }); - return true; - } - - if (newSphereButton === toolBar.clicked(clickedOverlay)) { + addButton("newSphereButton", "sphere-01.svg", function() { createNewEntity({ type: "Sphere", dimensions: DEFAULT_DIMENSIONS, @@ -468,11 +281,9 @@ var toolBar = (function() { blue: 0 } }); + }); - return true; - } - - if (newLightButton === toolBar.clicked(clickedOverlay)) { + addButton("newLightButton", "light-01.svg", function() { createNewEntity({ type: "Light", dimensions: DEFAULT_LIGHT_DIMENSIONS, @@ -489,11 +300,9 @@ var toolBar = (function() { exponent: 0, cutoff: 180, // in degrees }); + }); - return true; - } - - if (newTextButton === toolBar.clicked(clickedOverlay)) { + addButton("newTextButton", "text-01.svg", function() { createNewEntity({ type: "Text", dimensions: { @@ -514,11 +323,9 @@ var toolBar = (function() { text: "some text", lineHeight: 0.06 }); + }); - return true; - } - - if (newWebButton === toolBar.clicked(clickedOverlay)) { + addButton("newWebButton", "web-01.svg", function() { createNewEntity({ type: "Web", dimensions: { @@ -528,11 +335,9 @@ var toolBar = (function() { }, sourceUrl: "https://highfidelity.com/", }); + }); - return true; - } - - if (newZoneButton === toolBar.clicked(clickedOverlay)) { + addButton("newZoneButton", "zone-01.svg", function() { createNewEntity({ type: "Zone", dimensions: { @@ -541,11 +346,9 @@ var toolBar = (function() { z: 10 }, }); + }); - return true; - } - - if (newParticleButton === toolBar.clicked(clickedOverlay)) { + addButton("newParticleButton", "particle-01.svg", function() { createNewEntity({ type: "ParticleEffect", isEmitting: true, @@ -567,33 +370,63 @@ var toolBar = (function() { emitRate: 100, textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", }); - } + }); - return false; + that.setActive(false); + } + + that.clearEntityList = function() { + entityListTool.clearEntityList(); }; - that.mouseReleaseEvent = function(event) { - return false; - } - - Window.domainChanged.connect(function() { - that.setActive(false); - that.clearEntityList(); - }); - - Entities.canAdjustLocksChanged.connect(function(canAdjustLocks) { - if (isActive && !canAdjustLocks) { - that.setActive(false); + that.setActive = function(active) { + if (active == isActive) { + return; } - }); - - that.cleanup = function() { - toolBar.cleanup(); + if (active && !Entities.canRez() && !Entities.canRezTmp()) { + Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } + Messages.sendLocalMessage("edit-events", JSON.stringify({ + enabled: active + })); + isActive = active; + Settings.setValue(EDIT_SETTING, active); + if (!isActive) { + entityListTool.setVisible(false); + gridTool.setVisible(false); + grid.setEnabled(false); + propertiesTool.setVisible(false); + selectionManager.clearSelections(); + cameraManager.disable(); + selectionDisplay.triggerMapping.disable(); + } else { + UserActivityLogger.enabledEdit(); + hasShownPropertiesTool = false; + entityListTool.setVisible(true); + gridTool.setVisible(true); + grid.setEnabled(true); + propertiesTool.setVisible(true); + selectionDisplay.triggerMapping.enable(); + // Not sure what the following was meant to accomplish, but it currently causes + // everybody else to think that Interface has lost focus overall. fogbugzid:558 + // Window.setFocus(); + } + // Sets visibility of tool buttons, excluding the power button + toolBar.writeProperty("shown", active); + var visible = toolBar.readProperty("visible"); + if (active && !visible) { + toolBar.writeProperty("shown", false); + toolBar.writeProperty("shown", true); + } + //toolBar.selectTool(activeButton, isActive); + lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); }; initialize(); return that; -}()); +})(); function isLocked(properties) { @@ -702,7 +535,7 @@ function mousePressEvent(event) { mouseHasMovedSincePress = false; mouseCapturedByTool = false; - if (propertyMenu.mousePressEvent(event) || toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { + if (propertyMenu.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { mouseCapturedByTool = true; return; } @@ -745,16 +578,6 @@ function mouseMove(event) { mouseHasMovedSincePress = true; } - if (placingEntityID) { - var pickRay = Camera.computePickRay(event.x, event.y); - var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE; - var offset = Vec3.multiply(distance, pickRay.direction); - var position = Vec3.sum(Camera.position, offset); - Entities.editEntity(placingEntityID, { - position: position, - }); - return; - } if (!isActive) { return; } @@ -786,20 +609,10 @@ function mouseReleaseEvent(event) { mouseMove(lastMouseMoveEvent); lastMouseMoveEvent = null; } - if (propertyMenu.mouseReleaseEvent(event) || toolBar.mouseReleaseEvent(event)) { - + if (propertyMenu.mouseReleaseEvent(event)) { return true; } - if (placingEntityID) { - - if (isActive) { - - selectionManager.setSelections([placingEntityID]); - } - placingEntityID = null; - } if (isActive && selectionManager.hasSelection()) { - tooltip.show(false); } if (mouseCapturedByTool) { @@ -1084,7 +897,6 @@ Script.scriptEnding.connect(function() { Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); progressDialog.cleanup(); - toolBar.cleanup(); cleanupModelMenus(); tooltip.cleanup(); selectionDisplay.cleanup(); @@ -1219,8 +1031,7 @@ function handeMenuEvent(menuItem) { if (!selectionManager.hasSelection()) { Window.alert("No entities have been selected."); } else { - var filename = "entities__" + Window.location.hostname + ".svo.json"; - filename = Window.save("Select where to save", filename, "*.json") + var filename = Window.save("Select Where to Save", "", "*.json") if (filename) { var success = Clipboard.exportEntities(filename, selectionManager.selections); if (!success) { @@ -1232,7 +1043,7 @@ function handeMenuEvent(menuItem) { var importURL = null; if (menuItem == "Import Entities") { - var fullPath = Window.browse("Select models to import", "", "*.json"); + var fullPath = Window.browse("Select Model to Import", "", "*.json"); if (fullPath) { importURL = "file:///" + fullPath; } @@ -1434,6 +1245,9 @@ function pushCommandForSelections(createdEntityData, deletedEntityData) { var entityID = SelectionManager.selections[i]; var initialProperties = SelectionManager.savedProperties[entityID]; var currentProperties = Entities.getEntityProperties(entityID); + if (!initialProperties) { + continue; + } undoData.setProperties.push({ entityID: entityID, properties: { diff --git a/scripts/system/examples.js b/scripts/system/examples.js index 9d33e473af..6fdb2a2874 100644 --- a/scripts/system/examples.js +++ b/scripts/system/examples.js @@ -9,10 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include([ - "libraries/toolBars.js", -]); - var toolIconUrl = Script.resolvePath("assets/images/tools/"); var EXAMPLES_URL = "https://metaverse.highfidelity.com/examples"; @@ -37,6 +33,8 @@ function showExamples(marketplaceID) { print("setting examples URL to " + url); examplesWindow.setURL(url); examplesWindow.setVisible(true); + + UserActivityLogger.openedMarketplace(); } function hideExamples() { @@ -52,87 +50,30 @@ function toggleExamples() { } } -var toolBar = (function() { - var that = {}, - toolBar, - browseExamplesButton; +var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - function initialize() { - toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.examples.toolbar", function(windowDimensions, toolbar) { - return { - x: windowDimensions.x / 2, - y: windowDimensions.y - }; - }, { - x: -toolWidth / 2, - y: -TOOLBAR_MARGIN_Y - toolHeight - }); - browseExamplesButton = toolBar.addTool({ - imageURL: toolIconUrl + "examples-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - visible: true, - showButtonDown: true - }); +var browseExamplesButton = toolBar.addButton({ + imageURL: toolIconUrl + "market.svg", + objectName: "examples", + buttonState: 1, + defaultState: 1, + hoverState: 3, + alpha: 0.9 +}); - toolBar.showTool(browseExamplesButton, true); - } +function onExamplesWindowVisibilityChanged() { + browseExamplesButton.writeProperty('buttonState', examplesWindow.visible ? 0 : 1); + browseExamplesButton.writeProperty('defaultState', examplesWindow.visible ? 0 : 1); + browseExamplesButton.writeProperty('hoverState', examplesWindow.visible ? 2 : 3); +} +function onClick() { + toggleExamples(); +} +browseExamplesButton.clicked.connect(onClick); +examplesWindow.visibleChanged.connect(onExamplesWindowVisibilityChanged); - var browseExamplesButtonDown = false; - that.mousePressEvent = function(event) { - var clickedOverlay, - 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 - }); - - if (browseExamplesButton === toolBar.clicked(clickedOverlay)) { - toggleExamples(); - return true; - } - - return false; - }; - - that.mouseReleaseEvent = function(event) { - var handled = false; - - - if (browseExamplesButtonDown) { - var clickedOverlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y - }); - } - - newModelButtonDown = false; - browseExamplesButtonDown = false; - - return handled; - } - - that.cleanup = function() { - toolBar.cleanup(); - }; - - initialize(); - return that; -}()); - -Controller.mousePressEvent.connect(toolBar.mousePressEvent) -Script.scriptEnding.connect(toolBar.cleanup); \ No newline at end of file +Script.scriptEnding.connect(function () { + toolBar.removeButton("examples"); + browseExamplesButton.clicked.disconnect(onClick); + examplesWindow.visibleChanged.disconnect(onExamplesWindowVisibilityChanged); +}); diff --git a/scripts/system/firstPersonHMD.js b/scripts/system/firstPersonHMD.js new file mode 100644 index 0000000000..082c6304be --- /dev/null +++ b/scripts/system/firstPersonHMD.js @@ -0,0 +1,17 @@ +// +// firstPersonHMD.js +// system +// +// Created by Zander Otavka on 6/24/16 +// Copyright 2016 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 +// + +// Automatically enter first person mode when entering HMD mode +HMD.displayModeChanged.connect(function(isHMDMode) { + if (isHMDMode) { + Camera.setModeString("first person"); + } +}); diff --git a/scripts/system/goto.js b/scripts/system/goto.js new file mode 100644 index 0000000000..2ed98c689b --- /dev/null +++ b/scripts/system/goto.js @@ -0,0 +1,40 @@ +// +// goto.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 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 toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + + +var button = toolBar.addButton({ + objectName: "goto", + imageURL: Script.resolvePath("assets/images/tools/directory.svg"), + visible: true, + buttonState: 1, + defaultState: 1, + hoverState: 3, + alpha: 0.9, +}); + +function onAddressBarShown(visible) { + button.writeProperty('buttonState', visible ? 0 : 1); + button.writeProperty('defaultState', visible ? 0 : 1); + button.writeProperty('hoverState', visible ? 2 : 3); +} +function onClicked(){ + DialogsManager.toggleAddressBar(); +} +button.clicked.connect(onClicked); +DialogsManager.addressBarShown.connect(onAddressBarShown); + +Script.scriptEnding.connect(function () { + toolBar.removeButton("goto"); + button.clicked.disconnect(onClicked); + DialogsManager.addressBarShown.disconnect(onAddressBarShown); +}); diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js new file mode 100644 index 0000000000..ac1918b001 --- /dev/null +++ b/scripts/system/hmd.js @@ -0,0 +1,52 @@ +// +// hmd.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 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 headset; // The preferred headset. Default to the first one found in the following list. +var displayMenuName = "Display"; +var desktopMenuItemName = "Desktop"; +['OpenVR (Vive)', 'Oculus Rift'].forEach(function (name) { + if (!headset && Menu.menuItemExists(displayMenuName, name)) { + headset = name; + } +}); + +var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); +var button; +function onHmdChanged(isHmd) { + button.writeProperty('buttonState', isHmd ? 0 : 1); + button.writeProperty('defaultState', isHmd ? 0 : 1); + button.writeProperty('hoverState', isHmd ? 2 : 3); +} +function onClicked(){ + var isDesktop = Menu.isOptionChecked(desktopMenuItemName); + Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true); +} +if (headset) { + button = toolBar.addButton({ + objectName: "hmdToggle", + imageURL: Script.resolvePath("assets/images/tools/switch.svg"), + visible: true, + hoverState: 2, + defaultState: 0, + alpha: 0.9, + }); + onHmdChanged(HMD.active); + + button.clicked.connect(onClicked); + HMD.displayModeChanged.connect(onHmdChanged); + + Script.scriptEnding.connect(function () { + toolBar.removeButton("hmdToggle"); + button.clicked.disconnect(onClicked); + HMD.displayModeChanged.disconnect(onHmdChanged); + }); +} + diff --git a/scripts/system/html/edit-style.css b/scripts/system/html/edit-style.css index 5eaa3c6497..19d1cd95a9 100644 --- a/scripts/system/html/edit-style.css +++ b/scripts/system/html/edit-style.css @@ -268,7 +268,7 @@ input[type=number]::-webkit-inner-spin-button { height: 100%; overflow: hidden; font-family: hifi-glyphs; - font-size: 50px; + font-size: 46px; color: #afafaf; cursor: pointer; /*background-color: #000000;*/ @@ -276,17 +276,17 @@ input[type=number]::-webkit-inner-spin-button { input[type=number]::-webkit-inner-spin-button:before, input[type=number]::-webkit-inner-spin-button:after { position:absolute; - left: -21px; + left: -19px; line-height: 8px; text-align: center; } input[type=number]::-webkit-inner-spin-button:before { content: "6"; - top: 5px; + top: 4px; } input[type=number]::-webkit-inner-spin-button:after { content: "5"; - bottom: 6px; + bottom: 4px; } input[type=number].hover-up::-webkit-inner-spin-button:before, @@ -613,7 +613,7 @@ hr { margin-right: -48px; position: relative; left: -12px; - top: -11px; + top: -9px; } .dropdown dd { diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index ae5684d6c8..f2ade39144 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -25,6 +25,7 @@ var ICON_FOR_TYPE = { Box: "V", Sphere: "n", + Shape: "n", ParticleEffect: "", Model: "", Web: "q", @@ -403,6 +404,10 @@ var elColorGreen = document.getElementById("property-color-green"); var elColorBlue = document.getElementById("property-color-blue"); + var elShapeSections = document.querySelectorAll(".shape-section"); + allSections.push(elShapeSections); + var elShape = document.getElementById("property-shape"); + var elLightSections = document.querySelectorAll(".light-section"); allSections.push(elLightSections); var elLightSpotLight = document.getElementById("property-light-spot-light"); @@ -447,6 +452,7 @@ var elTextText = document.getElementById("property-text-text"); var elTextLineHeight = document.getElementById("property-text-line-height"); var elTextTextColor = document.getElementById("property-text-text-color"); + var elTextFaceCamera = document.getElementById("property-text-face-camera"); var elTextTextColorRed = document.getElementById("property-text-text-color-red"); var elTextTextColorGreen = document.getElementById("property-text-text-color-green"); var elTextTextColorBlue = document.getElementById("property-text-text-color-blue"); @@ -484,6 +490,9 @@ var elZoneSkyboxColorGreen = document.getElementById("property-zone-skybox-color-green"); var elZoneSkyboxColorBlue = document.getElementById("property-zone-skybox-color-blue"); var elZoneSkyboxURL = document.getElementById("property-zone-skybox-url"); + + var elZoneFlyingAllowed = document.getElementById("property-zone-flying-allowed"); + var elZoneGhostingAllowed = document.getElementById("property-zone-ghosting-allowed"); var elPolyVoxSections = document.querySelectorAll(".poly-vox-section"); allSections.push(elPolyVoxSections); @@ -538,18 +547,10 @@ disableProperties(); } else { - var activeElement = document.activeElement; - - try { - var selected = (activeElement - && activeElement.selectionStart == 0 - && activeElement.selectionEnd == activeElement.value.length); - } catch (e) { - var selected = false; - } + properties = data.selections[0].properties; - + elID.innerHTML = properties.id; elType.innerHTML = properties.type; @@ -564,7 +565,6 @@ } else { enableProperties(); } - elName.value = properties.name; @@ -626,18 +626,19 @@ var parsedUserData = {} try { parsedUserData = JSON.parse(properties.userData); + + if ("grabbableKey" in parsedUserData) { + if ("grabbable" in parsedUserData["grabbableKey"]) { + elGrabbable.checked = parsedUserData["grabbableKey"].grabbable; + } + if ("wantsTrigger" in parsedUserData["grabbableKey"]) { + elWantsTrigger.checked = parsedUserData["grabbableKey"].wantsTrigger; + } + if ("ignoreIK" in parsedUserData["grabbableKey"]) { + elIgnoreIK.checked = parsedUserData["grabbableKey"].ignoreIK; + } + } } catch(e) {} - if ("grabbableKey" in parsedUserData) { - if ("grabbable" in parsedUserData["grabbableKey"]) { - elGrabbable.checked = parsedUserData["grabbableKey"].grabbable; - } - if ("wantsTrigger" in parsedUserData["grabbableKey"]) { - elWantsTrigger.checked = parsedUserData["grabbableKey"].wantsTrigger; - } - if ("ignoreIK" in parsedUserData["grabbableKey"]) { - elIgnoreIK.checked = parsedUserData["grabbableKey"].ignoreIK; - } - } elCollisionSoundURL.value = properties.collisionSoundURL; elLifetime.value = properties.lifetime; @@ -662,7 +663,20 @@ elHyperlinkSections[i].style.display = 'table'; } - if (properties.type == "Box" || properties.type == "Sphere" || properties.type == "ParticleEffect") { + if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere") { + for (var i = 0; i < elShapeSections.length; i++) { + elShapeSections[i].style.display = 'table'; + } + elShape.value = properties.shape; + setDropdownText(elShape); + + } else { + for (var i = 0; i < elShapeSections.length; i++) { + elShapeSections[i].style.display = 'none'; + } + } + + if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere" || properties.type == "ParticleEffect") { for (var i = 0; i < elColorSections.length; i++) { elColorSections[i].style.display = 'table'; } @@ -713,6 +727,7 @@ elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); + elTextFaceCamera = properties.faceCamera; elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + properties.textColor.green + "," + properties.textColor.blue + ")"; elTextTextColorRed.value = properties.textColor.red; elTextTextColorGreen.value = properties.textColor.green; @@ -770,6 +785,9 @@ elZoneSkyboxColorGreen.value = properties.skybox.color.green; elZoneSkyboxColorBlue.value = properties.skybox.color.blue; elZoneSkyboxURL.value = properties.skybox.url; + + elZoneFlyingAllowed.checked = properties.flyingAllowed; + elZoneGhostingAllowed.checked = properties.ghostingAllowed; showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox'); } else if (properties.type == "PolyVox") { @@ -786,9 +804,10 @@ elYTextureURL.value = properties.yTextureURL; elZTextureURL.value = properties.zTextureURL; } - - if (selected) { - activeElement.focus(); + + var activeElement = document.activeElement; + + if(typeof activeElement.select!=="undefined"){ activeElement.select(); } } @@ -951,6 +970,8 @@ elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent', 2)); elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff', 2)); + elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); + elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL')); @@ -969,8 +990,8 @@ elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); elTextText.addEventListener('change', createEmitTextPropertyUpdateFunction('text')); + elTextFaceCamera.addEventListener('change', createEmitCheckedPropertyUpdateFunction('faceCamera')); elTextLineHeight.addEventListener('change', createEmitNumberPropertyUpdateFunction('lineHeight')); - var textTextColorChangeFunction = createEmitColorPropertyUpdateFunction( 'textColor', elTextTextColorRed, elTextTextColorGreen, elTextTextColorBlue); elTextTextColorRed.addEventListener('change', textTextColorChangeFunction); @@ -1076,7 +1097,10 @@ })); elZoneSkyboxURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('skybox','url')); - + + elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed')); + elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed')); + var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction); @@ -1148,7 +1172,7 @@ for (var i = 0; i < els.length; i++) { var clicked = false; var originalText; - els[i].onfocus = function() { + els[i].onfocus = function(e) { originalText = this.value; this.select(); clicked = false; @@ -1321,6 +1345,20 @@
+
@@ -1334,7 +1372,6 @@
- @@ -1612,6 +1649,9 @@ + + +
@@ -1668,7 +1708,6 @@
-
M
@@ -1676,6 +1715,10 @@ +
+ + +
@@ -1707,6 +1750,14 @@
+
+ + +
+
+ + +
diff --git a/scripts/system/ignore.js b/scripts/system/ignore.js new file mode 100644 index 0000000000..1c996a7fcc --- /dev/null +++ b/scripts/system/ignore.js @@ -0,0 +1,214 @@ +// +// ignore.js +// scripts/system/ +// +// Created by Stephen Birarda on 07/11/2016 +// Copyright 2016 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 +// + +// grab the toolbar +var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + +// setup the ignore button and add it to the toolbar +var button = toolbar.addButton({ + objectName: 'ignore', + imageURL: Script.resolvePath("assets/images/tools/ignore.svg"), + visible: true, + buttonState: 1, + defaultState: 2, + hoverState: 3, + alpha: 0.9 +}); + +var isShowingOverlays = false; +var ignoreOverlays = {}; + +function removeOverlays() { + // enumerate the overlays and remove them + var ignoreOverlayKeys = Object.keys(ignoreOverlays); + + for (i = 0; i < ignoreOverlayKeys.length; ++i) { + var avatarID = ignoreOverlayKeys[i]; + Overlays.deleteOverlay(ignoreOverlays[avatarID]); + } + + ignoreOverlays = {}; +} + +// handle clicks on the toolbar button +function buttonClicked(){ + if (isShowingOverlays) { + removeOverlays(); + isShowingOverlays = false; + } else { + isShowingOverlays = true; + } + + button.writeProperty('buttonState', isShowingOverlays ? 0 : 1); + button.writeProperty('defaultState', isShowingOverlays ? 0 : 1); + button.writeProperty('hoverState', isShowingOverlays ? 2 : 3); +} + +button.clicked.connect(buttonClicked); + +function updateOverlays() { + if (isShowingOverlays) { + + var identifiers = AvatarList.getAvatarIdentifiers(); + + for (i = 0; i < identifiers.length; ++i) { + var avatarID = identifiers[i]; + + if (avatarID === null) { + // this is our avatar, skip it + continue; + } + + // get the position for this avatar + var avatar = AvatarList.getAvatar(avatarID); + var avatarPosition = avatar && avatar.position; + + if (!avatarPosition) { + // we don't have a valid position for this avatar, skip it + continue; + } + + // setup a position for the overlay that is just above this avatar's head + var overlayPosition = avatar.getJointPosition("Head"); + overlayPosition.y += 0.45; + + if (avatarID in ignoreOverlays) { + // keep the overlay above the current position of this avatar + Overlays.editOverlay(ignoreOverlays[avatarID], { + position: overlayPosition + }); + } else { + // add the overlay above this avatar + var newOverlay = Overlays.addOverlay("image3d", { + url: Script.resolvePath("assets/images/ignore-target-01.svg"), + position: overlayPosition, + size: 0.4, + scale: 0.4, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + solid: true, + isFacingAvatar: true, + drawInFront: true + }); + + // push this overlay to our array of overlays + ignoreOverlays[avatarID] = newOverlay; + } + } + } +} + +Script.update.connect(updateOverlays); + +AvatarList.avatarRemovedEvent.connect(function(avatarID){ + if (isShowingOverlays) { + // we are currently showing overlays and an avatar just went away + + // first remove the rendered overlay + Overlays.deleteOverlay(ignoreOverlays[avatarID]); + + // delete the saved ID of the overlay from our ignored overlays object + delete ignoreOverlays[avatarID]; + } +}); + +function handleSelectedOverlay(clickedOverlay) { + // see this is one of our ignore overlays + + var ignoreOverlayKeys = Object.keys(ignoreOverlays) + for (i = 0; i < ignoreOverlayKeys.length; ++i) { + var avatarID = ignoreOverlayKeys[i]; + var ignoreOverlay = ignoreOverlays[avatarID]; + + if (clickedOverlay.overlayID == ignoreOverlay) { + // matched to an overlay, ask for the matching avatar to be ignored + Users.ignore(avatarID); + + // cleanup of the overlay is handled by the connection to avatarRemovedEvent + } + } +} + +Controller.mousePressEvent.connect(function(event){ + if (isShowingOverlays) { + // handle click events so we can detect when our overlays are clicked + + if (!event.isLeftButton) { + // if another mouse button than left is pressed ignore it + return false; + } + + // compute the pick ray from the event + var pickRay = Camera.computePickRay(event.x, event.y); + + // grab the clicked overlay for the given pick ray + var clickedOverlay = Overlays.findRayIntersection(pickRay); + if (clickedOverlay.intersects) { + handleSelectedOverlay(clickedOverlay); + } + } +}); + +// We get mouseMoveEvents from the handControllers, via handControllerPointer. +// But we dont' get mousePressEvents. +var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + +var TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. +var TRIGGER_ON_VALUE = 0.4; +var TRIGGER_OFF_VALUE = 0.15; +var triggered = false; +var activeHand = Controller.Standard.RightHand; + +function controllerComputePickRay() { + var controllerPose = Controller.getPoseValue(activeHand); + if (controllerPose.valid && triggered) { + var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), + MyAvatar.position); + // This gets point direction right, but if you want general quaternion it would be more complicated: + var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation)); + return { origin: controllerPosition, direction: controllerDirection }; + } +} + +function makeTriggerHandler(hand) { + return function (value) { + if (isShowingOverlays) { + if (!triggered && (value > TRIGGER_GRAB_VALUE)) { // should we smooth? + triggered = true; + if (activeHand !== hand) { + // No switching while the other is already triggered, so no need to release. + activeHand = (activeHand === Controller.Standard.RightHand) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; + } + + var pickRay = controllerComputePickRay(); + if (pickRay) { + var overlayIntersection = Overlays.findRayIntersection(pickRay); + if (overlayIntersection.intersects) { + handleSelectedOverlay(overlayIntersection); + } + } + } else if (triggered && (value < TRIGGER_OFF_VALUE)) { + triggered = false; + } + } + }; +} +triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); +triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); + +triggerMapping.enable(); + +// cleanup the toolbar button and overlays when script is stopped +Script.scriptEnding.connect(function() { + toolbar.removeButton('ignore'); + removeOverlays(); + triggerMapping.disable(); +}); diff --git a/scripts/system/libraries/Trigger.js b/scripts/system/libraries/Trigger.js new file mode 100644 index 0000000000..ffde021f5d --- /dev/null +++ b/scripts/system/libraries/Trigger.js @@ -0,0 +1,87 @@ +"use strict"; + +/*jslint vars: true, plusplus: true*/ +/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print*/ + +Trigger = function(properties) { + properties = properties || {}; + var that = this; + that.label = properties.label || Math.random(); + that.SMOOTH_RATIO = properties.smooth || 0.1; // Time averaging of trigger - 0.0 disables smoothing + that.DEADZONE = properties.deadzone || 0.10; // Once pressed, a trigger must fall below the deadzone to be considered un-pressed once pressed. + that.HYSTERESIS = properties.hystersis || 0.05; // If not pressed, a trigger must go above DEADZONE + HYSTERSIS to be considered pressed + + that.value = 0; + that.pressed = false; + that.clicked = false; + + // Handlers + that.onPress = properties.onPress || function(){ + print("Pressed trigger " + that.label) + }; + that.onRelease = properties.onRelease || function(){ + print("Released trigger " + that.label) + }; + that.onClick = properties.onClick || function(){ + print("Clicked trigger " + that.label) + }; + that.onUnclick = properties.onUnclick || function(){ + print("Unclicked trigger " + that.label) + }; + + // Getters + that.isPressed = function() { + return that.pressed; + } + + that.isClicked = function() { + return that.clicked; + } + + that.getValue = function() { + return that.value; + } + + + // Private values + var controller = properties.controller || Controller.Standard.LT; + var controllerClick = properties.controllerClick || Controller.Standard.LTClick; + that.mapping = Controller.newMapping('com.highfidelity.controller.trigger.' + controller + '-' + controllerClick + '.' + that.label + Math.random()); + Script.scriptEnding.connect(that.mapping.disable); + + // Setup mapping, + that.mapping.from(controller).peek().to(function(value) { + that.value = (that.value * that.SMOOTH_RATIO) + + (value * (1.0 - that.SMOOTH_RATIO)); + + var oldPressed = that.pressed; + if (!that.pressed && that.value >= (that.DEADZONE + that.HYSTERESIS)) { + that.pressed = true; + that.onPress(); + } + + if (that.pressed && that.value < that.HYSTERESIS) { + that.pressed = false; + that.onRelease(); + } + }); + + that.mapping.from(controllerClick).peek().to(function(value){ + if (!that.clicked && value > 0.0) { + that.clicked = true; + that.onClick(); + } + if (that.clicked && value == 0.0) { + that.clicked = false; + that.onUnclick(); + } + }); + + that.enable = function() { + that.mapping.enable(); + } + + that.disable = function() { + that.mapping.disable(); + } +} diff --git a/scripts/system/libraries/Xform.js b/scripts/system/libraries/Xform.js new file mode 100644 index 0000000000..1aa43cf30b --- /dev/null +++ b/scripts/system/libraries/Xform.js @@ -0,0 +1,48 @@ +// +// Created by Anthony J. Thibault on 2016/06/21 +// Copyright 2016 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 +// + +// ctor +Xform = function(rot, pos) { + this.rot = rot; + this.pos = pos; +} + +Xform.ident = function() { + return new Xform({x: 0, y: 0, z: 0, w: 1}, {x: 0, y: 0, z: 0}); +}; + +Xform.mul = function(lhs, rhs) { + var rot = Quat.multiply(lhs.rot, rhs.rot); + var pos = Vec3.sum(lhs.pos, Vec3.multiplyQbyV(lhs.rot, rhs.pos)); + return new Xform(rot, pos); +}; + +Xform.prototype.inv = function() { + var invRot = Quat.inverse(this.rot); + var invPos = Vec3.multiply(-1, this.pos); + return new Xform(invRot, Vec3.multiplyQbyV(invRot, invPos)); +}; + +Xform.prototype.mirrorX = function() { + return new Xform({x: this.rot.x, y: -this.rot.y, z: -this.rot.z, w: this.rot.w}, + {x: -this.pos.x, y: this.pos.y, z: this.pos.z}); +}; + +Xform.prototype.xformVector = function (vector) { + return Vec3.multiplyQbyV(this.rot, vector); +} + +Xform.prototype.xformPoint = function (point) { + return Vec3.sum(Vec3.multiplyQbyV(this.rot, point), this.pos); +} + +Xform.prototype.toString = function() { + var rot = this.rot; + var pos = this.pos; + return "Xform rot = (" + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "), pos = (" + pos.x + ", " + pos.y + ", " + pos.z + ")"; +}; diff --git a/scripts/system/libraries/entityCameraTool.js b/scripts/system/libraries/entityCameraTool.js index a8e9335956..301b60f550 100644 --- a/scripts/system/libraries/entityCameraTool.js +++ b/scripts/system/libraries/entityCameraTool.js @@ -141,7 +141,9 @@ CameraManager = function() { }; that.enable = function() { - if (Camera.mode == "independent" || that.enabled) return; + if (Camera.mode == "independent" || that.enabled || HMD.active) { + return; + } for (var i = 0; i < CAPTURED_KEYS.length; i++) { Controller.captureKeyEvents({ @@ -179,7 +181,9 @@ CameraManager = function() { } that.disable = function(ignoreCamera) { - if (!that.enabled) return; + if (!that.enabled) { + return; + } for (var i = 0; i < CAPTURED_KEYS.length; i++) { Controller.releaseKeyEvents({ @@ -352,27 +356,21 @@ CameraManager = function() { that.mousePressEvent = function(event) { if (cameraTool.mousePressEvent(event)) { - return true; } - - if (!that.enabled) return; + if (!that.enabled) { + return; + } if (event.isRightButton || (event.isLeftButton && event.isControl && !event.isShifted)) { - that.mode = MODE_ORBIT; } else if (event.isMiddleButton || (event.isLeftButton && event.isControl && event.isShifted)) { - - that.mode = MODE_PAN; } if (that.mode !== MODE_INACTIVE) { - - hasDragged = false; - return true; } @@ -381,7 +379,9 @@ CameraManager = function() { that.mouseReleaseEvent = function(event) { - if (!that.enabled) return; + if (!that.enabled) { + return; + } that.mode = MODE_INACTIVE; Reticle.setVisible(true); @@ -403,7 +403,9 @@ CameraManager = function() { }; that.wheelEvent = function(event) { - if (!that.enabled) return; + if (!that.enabled) { + return; + } var dZoom = -event.delta * SCROLL_SENSITIVITY; @@ -459,8 +461,12 @@ CameraManager = function() { } function normalizeDegrees(degrees) { - while (degrees > 180) degrees -= 360; - while (degrees < -180) degrees += 360; + while (degrees > 180) { + degrees -= 360; + } + while (degrees < -180) { + degrees += 360; + } return degrees; } @@ -483,7 +489,6 @@ CameraManager = function() { that.targetZoomDistance = clamp(that.targetZoomDistance, MIN_ZOOM_DISTANCE, MAX_ZOOM_DISTANCE); } - if (easing) { easingTime = Math.min(EASE_TIME, easingTime + dt); } diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index e9baeac86c..448293a2ac 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -111,8 +111,8 @@ EntityListTool = function(opts) { } }); - webView.visibilityChanged.connect(function (visible) { - if (visible) { + webView.visibleChanged.connect(function () { + if (webView.visible) { that.sendUpdate(); } }); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index e46306ca31..2003df3652 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1005,6 +1005,56 @@ SelectionDisplay = (function() { var activeTool = null; var grabberTools = {}; + // We get mouseMoveEvents from the handControllers, via handControllerPointer. + // But we dont' get mousePressEvents. + that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + Script.scriptEnding.connect(that.triggerMapping.disable); + that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. + that.TRIGGER_ON_VALUE = 0.4; + that.TRIGGER_OFF_VALUE = 0.15; + that.triggered = false; + var activeHand = Controller.Standard.RightHand; + function makeTriggerHandler(hand) { + return function (value) { + if (!that.triggered && (value > that.TRIGGER_GRAB_VALUE)) { // should we smooth? + that.triggered = true; + if (activeHand !== hand) { + // No switching while the other is already triggered, so no need to release. + activeHand = (activeHand === Controller.Standard.RightHand) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; + } + var eventResult = that.mousePressEvent({}); + if (!eventResult || (eventResult === 'selectionBox')) { + var pickRay = controllerComputePickRay(); + if (pickRay) { + var entityIntersection = Entities.findRayIntersection(pickRay, true); + var overlayIntersection = Overlays.findRayIntersection(pickRay); + if (entityIntersection.intersects && + (!overlayIntersection.intersects || (entityIntersection.distance < overlayIntersection.distance))) { + selectionManager.setSelections([entityIntersection.entityID]); + } + } + } + } else if (that.triggered && (value < that.TRIGGER_OFF_VALUE)) { + that.triggered = false; + that.mouseReleaseEvent({}); + } + }; + } + that.triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); + that.triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); + function controllerComputePickRay() { + var controllerPose = Controller.getPoseValue(activeHand); + if (controllerPose.valid && that.triggered) { + var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), + MyAvatar.position); + // This gets point direction right, but if you want general quaternion it would be more complicated: + var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation)); + return {origin: controllerPosition, direction: controllerDirection}; + } + } + function generalComputePickRay(x, y) { + return controllerComputePickRay() || Camera.computePickRay(x, y); + } function addGrabberTool(overlay, tool) { grabberTools[overlay] = { mode: tool.mode, @@ -1047,7 +1097,7 @@ SelectionDisplay = (function() { lastCameraOrientation = Camera.getOrientation(); if (event !== false) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); var wantDebug = false; if (wantDebug) { @@ -2269,7 +2319,7 @@ SelectionDisplay = (function() { startPosition = SelectionManager.worldPosition; var dimensions = SelectionManager.worldDimensions; - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { x: 0, y: 1, @@ -2312,7 +2362,7 @@ SelectionDisplay = (function() { }, onMove: function(event) { var wantDebug = false; - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { x: 0, @@ -2422,6 +2472,9 @@ SelectionDisplay = (function() { for (var i = 0; i < SelectionManager.selections.length; i++) { var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; + if (!properties) { + continue; + } var newPosition = Vec3.sum(properties.position, { x: vector.x, y: 0, @@ -2448,7 +2501,7 @@ SelectionDisplay = (function() { addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", onBegin: function(event) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); upDownPickNormal = Quat.getFront(lastCameraOrientation); // Remove y component so the y-axis lies along the plane we picking on - this will @@ -2481,7 +2534,7 @@ SelectionDisplay = (function() { pushCommandForSelections(duplicatedEntityIDs); }, onMove: function(event) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); // translate mode left/right based on view toward entity var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); @@ -2690,7 +2743,7 @@ SelectionDisplay = (function() { } } planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); lastPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); @@ -2726,7 +2779,7 @@ SelectionDisplay = (function() { rotation = SelectionManager.worldRotation; } - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); newPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); @@ -2782,10 +2835,10 @@ SelectionDisplay = (function() { var wantDebug = false; if (wantDebug) { print(stretchMode); - Vec3.print(" newIntersection:", newIntersection); + //Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - Vec3.print(" oldPOS:", oldPOS); - Vec3.print(" newPOS:", newPOS); + //Vec3.print(" oldPOS:", oldPOS); + //Vec3.print(" newPOS:", newPOS); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); @@ -3350,7 +3403,7 @@ SelectionDisplay = (function() { pushCommandForSelections(); }, onMove: function(event) { - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false @@ -3520,7 +3573,7 @@ SelectionDisplay = (function() { pushCommandForSelections(); }, onMove: function(event) { - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false @@ -3682,7 +3735,7 @@ SelectionDisplay = (function() { pushCommandForSelections(); }, onMove: function(event) { - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false @@ -3797,13 +3850,13 @@ SelectionDisplay = (function() { that.mousePressEvent = function(event) { var wantDebug = false; - if (!event.isLeftButton) { + if (!event.isLeftButton && !that.triggered) { // if another mouse button than left is pressed ignore it return false; } var somethingClicked = false; - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); // before we do a ray test for grabbers, disable the ray intersection for our selection box Overlays.editOverlay(selectionBox, { @@ -3837,7 +3890,7 @@ SelectionDisplay = (function() { if (tool) { activeTool = tool; mode = tool.mode; - somethingClicked = true; + somethingClicked = 'tool'; if (activeTool && activeTool.onBegin) { activeTool.onBegin(event); } @@ -3845,7 +3898,7 @@ SelectionDisplay = (function() { switch (result.overlayID) { case grabberMoveUp: mode = "TRANSLATE_UP_DOWN"; - somethingClicked = true; + somethingClicked = mode; // in translate mode, we hide our stretch handles... for (var i = 0; i < stretchHandles.length; i++) { @@ -3860,34 +3913,34 @@ SelectionDisplay = (function() { case grabberEdgeTN: // TODO: maybe this should be TOP+NEAR stretching? case grabberEdgeBN: // TODO: maybe this should be BOTTOM+FAR stretching? mode = "STRETCH_NEAR"; - somethingClicked = true; + somethingClicked = mode; break; case grabberFAR: case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching? case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching? mode = "STRETCH_FAR"; - somethingClicked = true; + somethingClicked = mode; break; case grabberTOP: mode = "STRETCH_TOP"; - somethingClicked = true; + somethingClicked = mode; break; case grabberBOTTOM: mode = "STRETCH_BOTTOM"; - somethingClicked = true; + somethingClicked = mode; break; case grabberRIGHT: case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching? case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching? mode = "STRETCH_RIGHT"; - somethingClicked = true; + somethingClicked = mode; break; case grabberLEFT: case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching? case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching? mode = "STRETCH_LEFT"; - somethingClicked = true; + somethingClicked = mode; break; default: @@ -3955,7 +4008,7 @@ SelectionDisplay = (function() { if (tool) { activeTool = tool; mode = tool.mode; - somethingClicked = true; + somethingClicked = 'tool'; if (activeTool && activeTool.onBegin) { activeTool.onBegin(event); } @@ -3963,7 +4016,7 @@ SelectionDisplay = (function() { switch (result.overlayID) { case yawHandle: mode = "ROTATE_YAW"; - somethingClicked = true; + somethingClicked = mode; overlayOrientation = yawHandleRotation; overlayCenter = yawCenter; yawZero = result.intersection; @@ -3973,7 +4026,7 @@ SelectionDisplay = (function() { case pitchHandle: mode = "ROTATE_PITCH"; initialPosition = SelectionManager.worldPosition; - somethingClicked = true; + somethingClicked = mode; overlayOrientation = pitchHandleRotation; overlayCenter = pitchCenter; pitchZero = result.intersection; @@ -3982,7 +4035,7 @@ SelectionDisplay = (function() { case rollHandle: mode = "ROTATE_ROLL"; - somethingClicked = true; + somethingClicked = mode; overlayOrientation = rollHandleRotation; overlayCenter = rollCenter; rollZero = result.intersection; @@ -4156,7 +4209,7 @@ SelectionDisplay = (function() { mode = translateXZTool.mode; activeTool.onBegin(event); - somethingClicked = true; + somethingClicked = 'selectionBox'; break; default: if (wantDebug) { @@ -4169,7 +4222,7 @@ SelectionDisplay = (function() { } if (somethingClicked) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); if (wantDebug) { print("mousePressEvent()...... " + overlayNames[result.overlayID]); } @@ -4201,7 +4254,7 @@ SelectionDisplay = (function() { } // if no tool is active, then just look for handles to highlight... - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); var result = Overlays.findRayIntersection(pickRay); var pickedColor; var pickedAlpha; @@ -4320,7 +4373,7 @@ SelectionDisplay = (function() { that.updateHandleSizes = function() { if (selectionManager.hasSelection()) { var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); - var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 2; + var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5; var dimensions = SelectionManager.worldDimensions; var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; grabberSize = Math.min(grabberSize, avgDimension / 10); diff --git a/scripts/system/libraries/toolBars.js b/scripts/system/libraries/toolBars.js index d97575d349..5a84bf9027 100644 --- a/scripts/system/libraries/toolBars.js +++ b/scripts/system/libraries/toolBars.js @@ -56,6 +56,10 @@ Overlay2D = function(properties, overlay) { // overlay is an optional variable properties.alpha = alpha; Overlays.editOverlay(overlay, { alpha: alpha }); } + this.setImageURL = function(imageURL) { + properties.imageURL = imageURL; + Overlays.editOverlay(overlay, { imageURL: imageURL }); + } this.show = function(doShow) { properties.visible = doShow; Overlays.editOverlay(overlay, { visible: doShow }); @@ -252,9 +256,8 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit y: y - ToolBar.SPACING }); } - this.save(); } - + this.setAlpha = function(alpha, tool) { if(typeof(tool) === 'undefined') { for(var tool in this.tools) { @@ -268,7 +271,11 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit this.tools[tool].setAlpha(alpha); } } - + + this.setImageURL = function(imageURL, tool) { + this.tools[tool].setImageURL(imageURL); + } + this.setBack = function(color, alpha) { if (color == null) { Overlays.editOverlay(this.back, { visible: false }); @@ -413,6 +420,9 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit for (var tool in that.tools) { that.tools[tool].buttonDown(false); } + if (that.mightBeDragging) { + that.save(); + } } this.mouseMove = function (event) { if (!that.mightBeDragging || !event.isLeftButton) { @@ -478,4 +488,4 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit } ToolBar.SPACING = 6; ToolBar.VERTICAL = 0; -ToolBar.HORIZONTAL = 1; \ No newline at end of file +ToolBar.HORIZONTAL = 1; diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index f39f4d7913..b7de9b012c 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -309,5 +309,11 @@ calculateHandSizeRatio = function() { clamp = function(val, min, max){ return Math.max(min, Math.min(max, val)) - } +} +// flattens an array of arrays into a single array +// example: flatten([[1], [3, 4], []) => [1, 3, 4] +// NOTE: only does one level of flattening, it is not recursive. +flatten = function(array) { + return [].concat.apply([], array); +} diff --git a/scripts/system/mute.js b/scripts/system/mute.js new file mode 100644 index 0000000000..4ea8aee546 --- /dev/null +++ b/scripts/system/mute.js @@ -0,0 +1,46 @@ +// +// goto.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 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 toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + + +var button = toolBar.addButton({ + objectName: "mute", + imageURL: Script.resolvePath("assets/images/tools/mic.svg"), + visible: true, + buttonState: 1, + defaultState: 1, + hoverState: 3, + alpha: 0.9 +}); + +function onMuteToggled() { + // We could just toggle state, but we're less likely to get out of wack if we read the AudioDevice. + // muted => button "on" state => 1. go figure. + var state = AudioDevice.getMuted() ? 0 : 1; + var hoverState = AudioDevice.getMuted() ? 2 : 3; + button.writeProperty('buttonState', state); + button.writeProperty('defaultState', state); + button.writeProperty('hoverState', hoverState); +} +onMuteToggled(); +function onClicked(){ + var menuItem = "Mute Microphone"; + Menu.setIsOptionChecked(menuItem, !Menu.isOptionChecked(menuItem)); +} +button.clicked.connect(onClicked); +AudioDevice.muteToggled.connect(onMuteToggled); + +Script.scriptEnding.connect(function () { + toolBar.removeButton("mute"); + button.clicked.disconnect(onClicked); + AudioDevice.muteToggled.disconnect(onMuteToggled); +}); diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 7d97470b8a..98a31d708c 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -49,10 +49,10 @@ // 2. Declare a text string. // 3. Call createNotifications(text, NotificationType) parsing the text. // example: -// if (key.text === "s") { +// if (key.text === "o") { // if (ctrlIsPressed === true) { -// noteString = "Snapshot taken."; -// createNotification(noteString, NotificationType.SNAPSHOT); +// noteString = "Open script"; +// createNotification(noteString, NotificationType.OPEN_SCRIPT); // } // } @@ -233,8 +233,9 @@ function calculate3DOverlayPositions(noticeWidth, noticeHeight, y) { // Pushes data to each array and sets up data for 2nd dimension array // to handle auxiliary data not carried by the overlay class // specifically notification "heights", "times" of creation, and . -function notify(notice, button, height) { - var noticeWidth, +function notify(notice, button, height, imageProperties, image) { + var notificationText, + noticeWidth, noticeHeight, positions, last; @@ -246,20 +247,27 @@ function notify(notice, button, height) { noticeHeight = notice.height * NOTIFICATION_3D_SCALE; notice.size = { x: noticeWidth, y: noticeHeight }; - notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE; - notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; - notice.bottomMargin = 0; - notice.rightMargin = 0; - notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; - notice.isFacingAvatar = false; + + positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); + + if (!image) { + notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE; + notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; + notice.bottomMargin = 0; + notice.rightMargin = 0; + notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; + notice.isFacingAvatar = false; + + notificationText = Overlays.addOverlay("text3d", notice); + notifications.push(notificationText); + } else { + notifications.push(Overlays.addOverlay("image3d", notice)); + } button.url = button.imageURL; button.scale = button.width * NOTIFICATION_3D_SCALE; button.isFacingAvatar = false; - positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); - - notifications.push((Overlays.addOverlay("text3d", notice))); buttons.push((Overlays.addOverlay("image3d", button))); overlay3DDetails.push({ notificationOrientation: positions.notificationOrientation, @@ -269,23 +277,44 @@ function notify(notice, button, height) { height: noticeHeight }); } else { - var notificationText = Overlays.addOverlay("text", notice); - notifications.push((notificationText)); - buttons.push((Overlays.addOverlay("image", button))); + if (!image) { + notificationText = Overlays.addOverlay("text", notice); + notifications.push((notificationText)); + } else { + notifications.push(Overlays.addOverlay("image", notice)); + } + buttons.push(Overlays.addOverlay("image", button)); } height = height + 1.0; heights.push(height); times.push(new Date().getTime() / 1000); - myAlpha.push(0); last = notifications.length - 1; + myAlpha.push(notifications[last].alpha); createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]); fadeIn(notifications[last], buttons[last]); + + if (imageProperties && !image) { + var imageHeight = notice.width / imageProperties.aspectRatio; + notice = { + x: notice.x, + y: notice.y + height, + width: notice.width, + height: imageHeight, + subImage: { x: 0, y: 0 }, + color: { red: 255, green: 255, blue: 255}, + visible: true, + imageURL: imageProperties.path, + alpha: backgroundAlpha + }; + notify(notice, button, imageHeight, imageProperties, true); + } + return notificationText; } // This function creates and sizes the overlays -function createNotification(text, notificationType) { +function createNotification(text, notificationType, imageProperties) { var count = (text.match(/\n/g) || []).length, breakPoint = 43.0, // length when new line is added extraLine = 0, @@ -308,6 +337,7 @@ function createNotification(text, notificationType) { level = (stack + 20.0); height = height + extraLine; + noticeProperties = { x: overlayLocationX, y: level, @@ -336,12 +366,11 @@ function createNotification(text, notificationType) { }; if (Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) && - Menu.isOptionChecked(NotificationType.getMenuString(notificationType))) - { + Menu.isOptionChecked(NotificationType.getMenuString(notificationType))) { randomSounds.playRandom(); } - return notify(noticeProperties, buttonProperties, height); + return notify(noticeProperties, buttonProperties, height, imageProperties); } function deleteNotification(index) { @@ -362,21 +391,12 @@ function deleteNotification(index) { // wraps whole word to newline function stringDivider(str, slotWidth, spaceReplacer) { - var p, - left, - right; + var left, right; - if (str.length > slotWidth) { - p = slotWidth; - while (p > 0 && str[p] !== ' ') { - p -= 1; - } - - if (p > 0) { - left = str.substring(0, p); - right = str.substring(p + 1); - return left + spaceReplacer + stringDivider(right, slotWidth, spaceReplacer); - } + if (str.length > slotWidth && slotWidth > 0) { + left = str.substring(0, slotWidth); + right = str.substring(slotWidth); + return left + spaceReplacer + stringDivider(right, slotWidth, spaceReplacer); } return str; } @@ -504,7 +524,15 @@ function onMuteStateChanged() { } function onDomainConnectionRefused(reason) { - createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED ); + createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); +} + +function onSnapshotTaken(path) { + var imageProperties = { + path: Script.resolvePath("file:///" + path), + aspectRatio: Window.innerWidth / Window.innerHeight + } + createNotification(wordWrap("Snapshot saved to " + path), NotificationType.SNAPSHOT, imageProperties); } // handles mouse clicks on buttons @@ -541,13 +569,6 @@ function keyPressEvent(key) { if (key.key === 16777249) { ctrlIsPressed = true; } - - if (key.text === "s") { - if (ctrlIsPressed === true) { - noteString = "Snapshot taken."; - createNotification(noteString, NotificationType.SNAPSHOT); - } - } } function setup() { @@ -615,5 +636,6 @@ Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); Menu.menuItemEvent.connect(menuItemEvent); Window.domainConnectionRefused.connect(onDomainConnectionRefused); +Window.snapshotTaken.connect(onSnapshotTaken); setup(); diff --git a/scripts/system/selectAudioDevice.js b/scripts/system/selectAudioDevice.js index fd4cd43a59..663f96b59c 100644 --- a/scripts/system/selectAudioDevice.js +++ b/scripts/system/selectAudioDevice.js @@ -48,7 +48,7 @@ var selectedInputMenu = ""; var selectedOutputMenu = ""; function setupAudioMenus() { - Menu.addMenu("Audio > Devices", "Advanced"); + Menu.addMenu("Audio > Devices"); Menu.addSeparator("Audio > Devices","Output Audio Device"); var outputDeviceSetting = Settings.getValue(OUTPUT_DEVICE_SETTING); diff --git a/scripts/system/users.js b/scripts/system/users.js index d935dd23ca..853067b90b 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -344,6 +344,7 @@ var usersWindow = (function () { windowTextHeight, windowLineSpacing, windowLineHeight, // = windowTextHeight + windowLineSpacing + windowMinimumHeight, usersOnline, // Raw users data linesOfUsers = [], // Array of indexes pointing into usersOnline @@ -359,25 +360,29 @@ var usersWindow = (function () { usersTimer = null, USERS_UPDATE_TIMEOUT = 5000, // ms = 5s + showMe, myVisibility, - MENU_NAME = "Tools", + MENU_NAME = "View", MENU_ITEM = "Users Online", - MENU_ITEM_AFTER = "Chat...", + MENU_ITEM_AFTER = "Overlays", + SETTING_USERS_SHOW_ME = "UsersWindow.ShowMe", + SETTING_USERS_VISIBLE_TO = "UsersWindow.VisibleTo", SETTING_USERS_WINDOW_MINIMIZED = "UsersWindow.Minimized", - SETINGS_USERS_WINDOW_OFFSET = "UsersWindow.Offset", + SETTING_USERS_WINDOW_OFFSET = "UsersWindow.Offset", // +ve x, y values are offset from left, top of screen; -ve from right, bottom. + isLoggedIn = false, isVisible = true, - isMinimized = false, + isMinimized = true, isBorderVisible = false, viewport, isMirrorDisplay = false, isFullscreenMirror = false, - windowPosition = { }, // Bottom left corner of window pane. + windowPosition = {}, // Bottom left corner of window pane. isMovingWindow = false, movingClickOffset = { x: 0, y: 0 }, @@ -388,6 +393,12 @@ var usersWindow = (function () { scrollbarBarClickedAt, // 0.0 .. 1.0 scrollbarValue = 0.0; // 0.0 .. 1.0 + function isValueTrue(value) { + // Work around Boolean Settings values being read as string when Interface starts up but as Booleans when re-read after + // Being written if refresh script. + return value === true || value === "true"; + } + function calculateWindowHeight() { var AUDIO_METER_HEIGHT = 52, MIRROR_HEIGHT = 220, @@ -425,6 +436,11 @@ var usersWindow = (function () { } } + function saturateWindowPosition() { + windowPosition.x = Math.max(0, Math.min(viewport.x - WINDOW_WIDTH, windowPosition.x)); + windowPosition.y = Math.max(windowMinimumHeight, Math.min(viewport.y, windowPosition.y)); + } + function updateOverlayPositions() { // Overlay positions are all relative to windowPosition; windowPosition is the position of the windowPane overlay. var windowLeft = windowPosition.x, @@ -517,13 +533,13 @@ var usersWindow = (function () { scrollbarBackgroundHeight = numUsersToDisplay * windowLineHeight - windowLineSpacing / 2; Overlays.editOverlay(scrollbarBackground, { height: scrollbarBackgroundHeight, - visible: isUsingScrollbars + visible: isLoggedIn && isUsingScrollbars }); scrollbarBarHeight = Math.max(numUsersToDisplay / linesOfUsers.length * scrollbarBackgroundHeight, SCROLLBAR_BAR_MIN_HEIGHT); Overlays.editOverlay(scrollbarBar, { height: scrollbarBarHeight, - visible: isUsingScrollbars + visible: isLoggedIn && isUsingScrollbars }); } @@ -541,10 +557,45 @@ var usersWindow = (function () { }); } + function updateOverlayVisibility() { + Overlays.editOverlay(windowBorder, { + visible: isLoggedIn && isVisible && isBorderVisible + }); + Overlays.editOverlay(windowPane, { + visible: isLoggedIn && isVisible + }); + Overlays.editOverlay(windowHeading, { + visible: isLoggedIn && isVisible + }); + Overlays.editOverlay(minimizeButton, { + visible: isLoggedIn && isVisible + }); + Overlays.editOverlay(scrollbarBackground, { + visible: isLoggedIn && isVisible && isUsingScrollbars && !isMinimized + }); + Overlays.editOverlay(scrollbarBar, { + visible: isLoggedIn && isVisible && isUsingScrollbars && !isMinimized + }); + Overlays.editOverlay(friendsButton, { + visible: isLoggedIn && isVisible && !isMinimized + }); + displayControl.setVisible(isLoggedIn && isVisible && !isMinimized); + visibilityControl.setVisible(isLoggedIn && isVisible && !isMinimized); + } + + function checkLoggedIn() { + var wasLoggedIn = isLoggedIn; + + isLoggedIn = Account.isLoggedIn(); + if (isLoggedIn !== wasLoggedIn) { + updateOverlayVisibility(); + } + } + function pollUsers() { var url = API_URL; - if (displayControl.getValue() === DISPLAY_FRIENDS) { + if (showMe === DISPLAY_FRIENDS) { url += API_FRIENDS_FILTER; } @@ -599,6 +650,8 @@ var usersWindow = (function () { updateUsersDisplay(); updateOverlayPositions(); + checkLoggedIn(); + } else { print("Error: Request for users status returned " + usersRequest.status + " " + usersRequest.statusText); usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay. @@ -614,32 +667,6 @@ var usersWindow = (function () { usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay. }; - function updateOverlayVisibility() { - Overlays.editOverlay(windowBorder, { - visible: isVisible && isBorderVisible - }); - Overlays.editOverlay(windowPane, { - visible: isVisible - }); - Overlays.editOverlay(windowHeading, { - visible: isVisible - }); - Overlays.editOverlay(minimizeButton, { - visible: isVisible - }); - Overlays.editOverlay(scrollbarBackground, { - visible: isVisible && isUsingScrollbars && !isMinimized - }); - Overlays.editOverlay(scrollbarBar, { - visible: isVisible && isUsingScrollbars && !isMinimized - }); - Overlays.editOverlay(friendsButton, { - visible: isVisible && !isMinimized - }); - displayControl.setVisible(isVisible && !isMinimized); - visibilityControl.setVisible(isVisible && !isMinimized); - } - function setVisible(visible) { isVisible = visible; @@ -653,7 +680,6 @@ var usersWindow = (function () { } updateOverlayVisibility(); - } function setMinimized(minimized) { @@ -664,6 +690,7 @@ var usersWindow = (function () { } }); updateOverlayVisibility(); + Settings.setValue(SETTING_USERS_WINDOW_MINIMIZED, isMinimized); } function onMenuItemEvent(event) { @@ -674,9 +701,11 @@ var usersWindow = (function () { function onFindableByChanged(event) { if (VISIBILITY_VALUES.indexOf(event) !== -1) { + myVisibility = event; visibilityControl.setValue(event); + Settings.setValue(SETTING_USERS_VISIBLE_TO, myVisibility); } else { - print("Error: Unrecognized onFindableByChanged value: " + myVisibility); + print("Error: Unrecognized onFindableByChanged value: " + event); } } @@ -706,17 +735,21 @@ var usersWindow = (function () { usersTimer = null; } pollUsers(); + showMe = displayControl.getValue(); + Settings.setValue(SETTING_USERS_SHOW_ME, showMe); return; } if (visibilityControl.handleClick(clickedOverlay)) { - GlobalServices.findableBy = visibilityControl.getValue(); + myVisibility = visibilityControl.getValue(); + GlobalServices.findableBy = myVisibility; + Settings.setValue(SETTING_USERS_VISIBLE_TO, myVisibility); return; } if (clickedOverlay === windowPane) { - overlayX = event.x - WINDOW_MARGIN; + overlayX = event.x - windowPosition.x - WINDOW_MARGIN; overlayY = event.y - windowPosition.y + windowHeight - WINDOW_MARGIN - windowLineHeight; numLinesBefore = Math.round(overlayY / windowLineHeight); @@ -799,6 +832,10 @@ var usersWindow = (function () { function onMouseMoveEvent(event) { var isVisible; + if (!isLoggedIn) { + return; + } + if (isMovingScrollbar) { if (scrollbarBackgroundPosition.x - WINDOW_MARGIN <= event.x && event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH + WINDOW_MARGIN @@ -823,6 +860,8 @@ var usersWindow = (function () { x: event.x - movingClickOffset.x, y: event.y - movingClickOffset.y }; + + saturateWindowPosition(); calculateWindowHeight(); updateOverlayPositions(); updateUsersDisplay(); @@ -860,9 +899,11 @@ var usersWindow = (function () { if (isMovingWindow) { // Save offset of bottom of window to nearest edge of the window. - offset.x = (windowPosition.x + WINDOW_WIDTH / 2 < viewport.x / 2) ? windowPosition.x : windowPosition.x - viewport.x; - offset.y = (windowPosition.y < viewport.y / 2) ? windowPosition.y : windowPosition.y - viewport.y; - Settings.setValue(SETINGS_USERS_WINDOW_OFFSET, JSON.stringify(offset)); + offset.x = (windowPosition.x + WINDOW_WIDTH / 2 < viewport.x / 2) + ? windowPosition.x : windowPosition.x - viewport.x; + offset.y = (windowPosition.y < viewport.y / 2) + ? windowPosition.y : windowPosition.y - viewport.y; + Settings.setValue(SETTING_USERS_WINDOW_OFFSET, JSON.stringify(offset)); isMovingWindow = false; } } @@ -914,18 +955,18 @@ var usersWindow = (function () { windowTextHeight = Math.floor(Overlays.textSize(textSizeOverlay, "1").height); windowLineSpacing = Math.floor(Overlays.textSize(textSizeOverlay, "1\n2").height - 2 * windowTextHeight); windowLineHeight = windowTextHeight + windowLineSpacing; + windowMinimumHeight = windowTextHeight + WINDOW_MARGIN + WINDOW_BASE_MARGIN; Overlays.deleteOverlay(textSizeOverlay); viewport = Controller.getViewportDimensions(); - offsetSetting = Settings.getValue(SETINGS_USERS_WINDOW_OFFSET); + offsetSetting = Settings.getValue(SETTING_USERS_WINDOW_OFFSET); if (offsetSetting !== "") { - offset = JSON.parse(Settings.getValue(SETINGS_USERS_WINDOW_OFFSET)); + offset = JSON.parse(Settings.getValue(SETTING_USERS_WINDOW_OFFSET)); } if (offset.hasOwnProperty("x") && offset.hasOwnProperty("y")) { windowPosition.x = offset.x < 0 ? viewport.x + offset.x : offset.x; windowPosition.y = offset.y <= 0 ? viewport.y + offset.y : offset.y; - } else { hmdViewport = Controller.getRecommendedOverlayRect(); windowPosition = { @@ -934,6 +975,7 @@ var usersWindow = (function () { }; } + saturateWindowPosition(); calculateWindowHeight(); windowBorder = Overlays.addOverlay("rectangle", { @@ -944,7 +986,7 @@ var usersWindow = (function () { radius: WINDOW_BORDER_RADIUS, color: WINDOW_BORDER_COLOR, alpha: WINDOW_BORDER_ALPHA, - visible: isVisible && isBorderVisible + visible: false }); windowPane = Overlays.addOverlay("text", { @@ -960,7 +1002,7 @@ var usersWindow = (function () { backgroundAlpha: WINDOW_BACKGROUND_ALPHA, text: "", font: WINDOW_FONT, - visible: isVisible + visible: false }); windowHeading = Overlays.addOverlay("text", { @@ -975,7 +1017,7 @@ var usersWindow = (function () { backgroundAlpha: 0.0, text: "No users online", font: WINDOW_FONT, - visible: isVisible && !isMinimized + visible: false }); minimizeButton = Overlays.addOverlay("image", { @@ -992,7 +1034,7 @@ var usersWindow = (function () { }, color: MIN_MAX_BUTTON_COLOR, alpha: MIN_MAX_BUTTON_ALPHA, - visible: isVisible && !isMinimized + visible: false }); scrollbarBackgroundPosition = { @@ -1007,7 +1049,7 @@ var usersWindow = (function () { backgroundColor: SCROLLBAR_BACKGROUND_COLOR, backgroundAlpha: SCROLLBAR_BACKGROUND_ALPHA, text: "", - visible: isVisible && isUsingScrollbars && !isMinimized + visible: false }); scrollbarBarPosition = { @@ -1022,7 +1064,7 @@ var usersWindow = (function () { backgroundColor: SCROLLBAR_BAR_COLOR, backgroundAlpha: SCROLLBAR_BAR_ALPHA, text: "", - visible: isVisible && isUsingScrollbars && !isMinimized + visible: false }); friendsButton = Overlays.addOverlay("image", { @@ -1038,12 +1080,18 @@ var usersWindow = (function () { height: FRIENDS_BUTTON_SVG_HEIGHT }, color: FRIENDS_BUTTON_COLOR, - alpha: FRIENDS_BUTTON_ALPHA + alpha: FRIENDS_BUTTON_ALPHA, + visible: false }); + showMe = Settings.getValue(SETTING_USERS_SHOW_ME, ""); + if (DISPLAY_VALUES.indexOf(showMe) === -1) { + showMe = DISPLAY_EVERYONE; + } + displayControl = new PopUpMenu({ prompt: DISPLAY_PROMPT, - value: DISPLAY_VALUES[0], + value: showMe, values: DISPLAY_VALUES, displayValues: DISPLAY_DISPLAY_VALUES, x: 0, @@ -1065,13 +1113,12 @@ var usersWindow = (function () { popupBackgroundAlpha: DISPLAY_OPTIONS_BACKGROUND_ALPHA, buttonColor: MIN_MAX_BUTTON_COLOR, buttonAlpha: MIN_MAX_BUTTON_ALPHA, - visible: isVisible && !isMinimized + visible: false }); - myVisibility = GlobalServices.findableBy; + myVisibility = Settings.getValue(SETTING_USERS_VISIBLE_TO, ""); if (VISIBILITY_VALUES.indexOf(myVisibility) === -1) { - print("Error: Unrecognized findableBy value: " + myVisibility); - myVisibility = VISIBILITY_ALL; + myVisibility = VISIBILITY_FRIENDS; } visibilityControl = new PopUpMenu({ @@ -1098,7 +1145,7 @@ var usersWindow = (function () { popupBackgroundAlpha: DISPLAY_OPTIONS_BACKGROUND_ALPHA, buttonColor: MIN_MAX_BUTTON_COLOR, buttonAlpha: MIN_MAX_BUTTON_ALPHA, - visible: isVisible && !isMinimized + visible: false }); Controller.mousePressEvent.connect(onMousePressEvent); @@ -1121,12 +1168,10 @@ var usersWindow = (function () { pollUsers(); // Set minimized at end - setup code does not handle `minimized == false` correctly - setMinimized(Settings.getValue(SETTING_USERS_WINDOW_MINIMIZED, false)); + setMinimized(isValueTrue(Settings.getValue(SETTING_USERS_WINDOW_MINIMIZED, false))); } function tearDown() { - Settings.setValue(SETTING_USERS_WINDOW_MINIMIZED, isMinimized); - Menu.removeMenuItem(MENU_NAME, MENU_ITEM); Script.clearTimeout(usersTimer); diff --git a/scripts/tutorials/createDice.js b/scripts/tutorials/createDice.js index 00ec1184bc..8975578c66 100644 --- a/scripts/tutorials/createDice.js +++ b/scripts/tutorials/createDice.js @@ -46,11 +46,6 @@ var offButton = toolBar.addOverlay("image", { width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: "http://hifi-production.s3.amazonaws.com/tutorials/dice/close.png", - color: { - red: 255, - green: 255, - blue: 255 - }, alpha: 1 }); @@ -60,11 +55,6 @@ var deleteButton = toolBar.addOverlay("image", { width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: "http://hifi-production.s3.amazonaws.com/tutorials/dice/delete.png", - color: { - red: 255, - green: 255, - blue: 255 - }, alpha: 1 }); @@ -75,11 +65,6 @@ var diceButton = toolBar.addOverlay("image", { width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: diceIconURL, - color: { - red: 255, - green: 255, - blue: 255 - }, alpha: 1 }); diff --git a/scripts/tutorials/getDomainMetadata.js b/scripts/tutorials/getDomainMetadata.js new file mode 100644 index 0000000000..54c356ae7b --- /dev/null +++ b/scripts/tutorials/getDomainMetadata.js @@ -0,0 +1,129 @@ +// +// Created by Zach Pomerantz on June 16, 2016. +// Copyright 2016 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 SERVER = 'https://metaverse.highfidelity.com/api/v1'; + +var OVERLAY = null; + +// Every time you enter a domain, display the domain's metadata +location.hostChanged.connect(function(host) { + print('Detected host change:', host); + + // Fetch the domain ID from the metaverse + var placeData = request(SERVER + '/places/' + host); + if (!placeData) { + print('Cannot find place name - abandoning metadata request for', host); + return; + + } + var domainID = placeData.data.place.domain.id; + print('Domain ID:', domainID); + + // Fetch the domain metadata from the metaverse + var domainData = request(SERVER + '/domains/' + domainID); + print(SERVER + '/domains/' + domainID); + if (!domainData) { + print('Cannot find domain data - abandoning metadata request for', domainID); + return; + } + var metadata = domainData.domain; + print('Domain metadata:', JSON.stringify(metadata)); + + // Display the fetched metadata in an overlay + displayMetadata(host, metadata); +}); + +Script.scriptEnding.connect(clearMetadata); + +function displayMetadata(place, metadata) { + clearMetadata(); + + var COLOR_TEXT = { red: 255, green: 255, blue: 255 }; + var COLOR_BACKGROUND = { red: 0, green: 0, blue: 0 }; + var MARGIN = 200; + var STARTING_OPACITY = 0.8; + var FADE_AFTER_SEC = 2; + var FADE_FOR_SEC = 4; + + var fade_per_sec = STARTING_OPACITY / FADE_FOR_SEC; + var properties = { + color: COLOR_TEXT, + alpha: STARTING_OPACITY, + backgroundColor: COLOR_BACKGROUND, + backgroundAlpha: STARTING_OPACITY, + font: { size: 24 }, + x: MARGIN, + y: MARGIN + }; + + // Center the overlay on the screen + properties.width = Window.innerWidth - MARGIN*2; + properties.height = Window.innerHeight - MARGIN*2; + + // Parse the metadata into text + parsed = [ 'Welcome to ' + place + '!',, ]; + if (metadata.description) { + parsed.push(description); + } + if (metadata.tags && metadata.tags.length) { + parsed.push('Tags: ' + metadata.tags.join(',')); + } + if (metadata.capacity) { + parsed.push('Capacity (max users): ' + metadata.capacity); + } + if (metadata.maturity) { + parsed.push('Maturity: ' + metadata.maturity); + } + if (metadata.hosts && metadata.hosts.length) { + parsed.push('Hosts: ' + metadata.tags.join(',')); + } + if (metadata.online_users) { + parsed.push('Users online: ' + metadata.online_users); + } + + properties.text = parsed.join('\n\n'); + + // Display the overlay + OVERLAY = Overlays.addOverlay('text', properties); + + // Fade out the overlay over 10 seconds + !function() { + var overlay = OVERLAY; + var alpha = STARTING_OPACITY; + + var fade = function() { + // Only fade so long as the same overlay is up + if (overlay == OVERLAY) { + alpha -= fade_per_sec / 10; + if (alpha <= 0) { + clearMetadata(); + } else { + Overlays.editOverlay(overlay, { alpha: alpha, backgroundAlpha: alpha }); + Script.setTimeout(fade, 100); + } + } + }; + Script.setTimeout(fade, FADE_AFTER_SEC * 1000); + }(); +} + +function clearMetadata() { + if (OVERLAY) { + Overlays.deleteOverlay(OVERLAY); + } +} + +// Request JSON from a url, synchronously +function request(url) { + var req = new XMLHttpRequest(); + req.responseType = 'json'; + req.open('GET', url, false); + req.send(); + return req.status == 200 ? req.response : null; +} + diff --git a/server-console/src/main.js b/server-console/src/main.js index 6465f6b0a3..8f85872d0b 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -515,6 +515,20 @@ function maybeInstallDefaultContentSet(onComplete) { return; } + console.log("Found contentPath:" + argv.contentPath); + if (argv.contentPath) { + fs.copy(argv.contentPath, getRootHifiDataDirectory(), function (err) { + if (err) { + console.log('Could not copy home content: ' + err); + return console.error(err) + } + console.log('Copied home content over to: ' + getRootHifiDataDirectory()); + onComplete(); + }); + return; + } + + // Show popup var window = new BrowserWindow({ icon: appIcon, diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt index cf1152da02..3aac4db0a8 100644 --- a/tests/controllers/CMakeLists.txt +++ b/tests/controllers/CMakeLists.txt @@ -6,7 +6,7 @@ setup_hifi_project(Script Qml) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries -link_hifi_libraries(shared gl script-engine plugins render-utils input-plugins display-plugins controllers) +link_hifi_libraries(shared gl script-engine plugins render-utils ui-plugins input-plugins display-plugins controllers) if (WIN32) diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index e978dd9a38..1a4f8742e9 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -34,10 +34,11 @@ #include #include -#include +#include #include #include #include +#include #include #include @@ -89,9 +90,9 @@ public: virtual GLWidget* getPrimaryWidget() override { return nullptr; } virtual MainWindow* getPrimaryWindow() override { return nullptr; } virtual QOpenGLContext* getPrimaryContext() override { return nullptr; } - virtual ui::Menu* getPrimaryMenu() { return nullptr; } - virtual bool isForeground() override { return true; } - virtual const DisplayPluginPointer getActiveDisplayPlugin() const override { return DisplayPluginPointer(); } + virtual ui::Menu* getPrimaryMenu() override { return nullptr; } + virtual bool isForeground() const override { return true; } + virtual DisplayPluginPointer getActiveDisplayPlugin() const override { return DisplayPluginPointer(); } }; class MyControllerScriptingInterface : public controller::ScriptingInterface { @@ -121,7 +122,7 @@ int main(int argc, char** argv) { }; foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { - inputPlugin->pluginUpdate(delta, calibrationData, false); + inputPlugin->pluginUpdate(delta, calibrationData); } auto userInputMapper = DependencyManager::get(); @@ -144,7 +145,10 @@ int main(int argc, char** argv) { if (name == KeyboardMouseDevice::NAME) { userInputMapper->registerDevice(std::dynamic_pointer_cast(inputPlugin)->getInputDevice()); } - inputPlugin->pluginUpdate(0, calibrationData, false); + if (name == TouchscreenDevice::NAME) { + userInputMapper->registerDevice(std::dynamic_pointer_cast(inputPlugin)->getInputDevice()); + } + inputPlugin->pluginUpdate(0, calibrationData); } rootContext->setContextProperty("Controllers", new MyControllerScriptingInterface()); } diff --git a/tests/entities/src/main.cpp b/tests/entities/src/main.cpp index c0e21276d8..792ef7d9c6 100644 --- a/tests/entities/src/main.cpp +++ b/tests/entities/src/main.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include #include @@ -155,7 +155,7 @@ int main(int argc, char** argv) { QFile file(getTestResourceDir() + "packet.bin"); if (!file.open(QIODevice::ReadOnly)) return -1; QByteArray packet = file.readAll(); - EntityItemPointer item = BoxEntityItem::factory(EntityItemID(), EntityItemProperties()); + EntityItemPointer item = ShapeEntityItem::boxFactory(EntityItemID(), EntityItemProperties()); ReadBitstreamToTreeParams params; params.bitstreamVersion = 33; diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index 4fc6143ff5..21ae9c5a99 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -4,4 +4,6 @@ AUTOSCRIBE_SHADER_LIB(gpu model render-utils) setup_hifi_project(Quick Gui OpenGL Script Widgets) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") link_hifi_libraries(networking gl gpu gpu-gl procedural shared fbx model model-networking animation script-engine render render-utils ) -package_libraries_for_deployment() \ No newline at end of file +package_libraries_for_deployment() + +target_nsight() diff --git a/tests/gpu-test/src/TestFbx.cpp b/tests/gpu-test/src/TestFbx.cpp new file mode 100644 index 0000000000..cea356e125 --- /dev/null +++ b/tests/gpu-test/src/TestFbx.cpp @@ -0,0 +1,187 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 "TestFbx.h" + +#include + +#include + +#include + +struct MyVertex { + vec3 position; + vec2 texCoords; + vec3 normal; + uint32_t color; + + static gpu::Stream::FormatPointer getVertexFormat() { + static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; + static const gpu::Element TEXTURE_ELEMENT { gpu::VEC2, gpu::FLOAT, gpu::UV }; + static const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; + static const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::NUINT8, gpu::RGBA }; + gpu::Stream::FormatPointer vertexFormat { std::make_shared() }; + vertexFormat->setAttribute(gpu::Stream::POSITION, 0, POSITION_ELEMENT, offsetof(MyVertex, position)); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD, 0, TEXTURE_ELEMENT, offsetof(MyVertex, texCoords)); + vertexFormat->setAttribute(gpu::Stream::COLOR, 0, COLOR_ELEMENT, offsetof(MyVertex, color)); + vertexFormat->setAttribute(gpu::Stream::NORMAL, 0, NORMAL_ELEMENT, offsetof(MyVertex, normal)); + return vertexFormat; + } + +}; + +struct Part { + size_t baseVertex; + size_t baseIndex; + size_t materialId; +}; + +struct DrawElementsIndirectCommand { + uint count { 0 }; + uint instanceCount { 1 }; + uint firstIndex { 0 }; + uint baseVertex { 0 }; + uint baseInstance { 0 }; +}; + + +class FileDownloader : public QObject { + Q_OBJECT +public: + explicit FileDownloader(QUrl imageUrl, QObject *parent = 0) : QObject(parent) { + connect(&m_WebCtrl, SIGNAL(finished(QNetworkReply*)), this, SLOT(fileDownloaded(QNetworkReply*))); + QNetworkRequest request(imageUrl); + m_WebCtrl.get(request); + } + + virtual ~FileDownloader() {} + + const QByteArray& downloadedData() const { + return m_DownloadedData; + } + +signals: + void downloaded(); + +private slots: + void fileDownloaded(QNetworkReply* pReply) { + m_DownloadedData = pReply->readAll(); + pReply->deleteLater(); + emit downloaded(); + } + +private: + QNetworkAccessManager m_WebCtrl; + QByteArray m_DownloadedData; +}; + + +static const QUrl TEST_ASSET = QString("https://s3.amazonaws.com/DreamingContent/assets/models/tardis/console.fbx"); +static const mat4 TEST_ASSET_TRANSFORM = glm::translate(mat4(), vec3(0, -1.5f, 0)) * glm::scale(mat4(), vec3(0.01f)); +//static const QUrl TEST_ASSET = QString("https://s3.amazonaws.com/DreamingContent/assets/simple/SimpleMilitary/Models/Vehicles/tank_02_c.fbx"); +//static const mat4 TEST_ASSET_TRANSFORM = glm::translate(mat4(), vec3(0, -0.5f, 0)) * glm::scale(mat4(), vec3(0.1f)); + +TestFbx::TestFbx(const render::ShapePlumberPointer& shapePlumber) : _shapePlumber(shapePlumber) { + FileDownloader* downloader = new FileDownloader(TEST_ASSET, qApp); + QObject::connect(downloader, &FileDownloader::downloaded, [this, downloader] { + parseFbx(downloader->downloadedData()); + }); +} + +bool TestFbx::isReady() const { + return _partCount != 0; +} + +void TestFbx::parseFbx(const QByteArray& fbxData) { + QVariantHash mapping; + FBXGeometry* fbx = readFBX(fbxData, mapping); + size_t totalVertexCount = 0; + size_t totalIndexCount = 0; + size_t totalPartCount = 0; + size_t highestIndex = 0; + for (const auto& mesh : fbx->meshes) { + size_t vertexCount = mesh.vertices.size(); + totalVertexCount += mesh.vertices.size(); + highestIndex = std::max(highestIndex, vertexCount); + totalPartCount += mesh.parts.size(); + for (const auto& part : mesh.parts) { + totalIndexCount += part.quadTrianglesIndices.size(); + totalIndexCount += part.triangleIndices.size(); + } + } + size_t baseVertex = 0; + std::vector vertices; + vertices.reserve(totalVertexCount); + std::vector indices; + indices.reserve(totalIndexCount); + std::vector parts; + parts.reserve(totalPartCount); + _partCount = totalPartCount; + for (const auto& mesh : fbx->meshes) { + baseVertex = vertices.size(); + + vec3 color; + for (const auto& part : mesh.parts) { + DrawElementsIndirectCommand partIndirect; + partIndirect.baseVertex = (uint)baseVertex; + partIndirect.firstIndex = (uint)indices.size(); + partIndirect.baseInstance = (uint)parts.size(); + _partTransforms.push_back(mesh.modelTransform); + auto material = fbx->materials[part.materialID]; + color = material.diffuseColor; + for (auto index : part.quadTrianglesIndices) { + indices.push_back(index); + } + for (auto index : part.triangleIndices) { + indices.push_back(index); + } + size_t triangles = (indices.size() - partIndirect.firstIndex); + Q_ASSERT(0 == (triangles % 3)); + //triangles /= 3; + partIndirect.count = (uint)triangles; + parts.push_back(partIndirect); + } + + size_t vertexCount = mesh.vertices.size(); + for (size_t i = 0; i < vertexCount; ++i) { + MyVertex vertex; + vertex.position = mesh.vertices[(int)i]; + vec3 n = mesh.normals[(int)i]; + vertex.normal = n; + vertex.texCoords = mesh.texCoords[(int)i]; + vertex.color = toCompactColor(vec4(color, 1)); + vertices.push_back(vertex); + } + } + + _vertexBuffer->append(vertices); + _indexBuffer->append(indices); + _indirectBuffer->append(parts); + delete fbx; +} + +void TestFbx::renderTest(size_t testId, RenderArgs* args) { + gpu::Batch& batch = *(args->_batch); + //pipeline->pipeline + if (_partCount) { + for (size_t i = 0; i < _partCount; ++i) { + batch.setModelTransform(TEST_ASSET_TRANSFORM * _partTransforms[i]); + batch.setupNamedCalls(__FUNCTION__, [this](gpu::Batch& batch, gpu::Batch::NamedBatchData&) { + RenderArgs args; args._batch = &batch; + _shapePlumber->pickPipeline(&args, render::ShapeKey()); + batch.setInputBuffer(0, _vertexBuffer, 0, sizeof(MyVertex)); + batch.setIndexBuffer(gpu::UINT16, _indexBuffer, 0); + batch.setInputFormat(MyVertex::getVertexFormat()); + batch.setIndirectBuffer(_indirectBuffer, 0); + batch.multiDrawIndexedIndirect((uint)_partCount, gpu::TRIANGLES); + }); + } + } +} + +#include "TestFbx.moc" diff --git a/tests/gpu-test/src/TestFbx.h b/tests/gpu-test/src/TestFbx.h new file mode 100644 index 0000000000..1f3c0bb50e --- /dev/null +++ b/tests/gpu-test/src/TestFbx.h @@ -0,0 +1,35 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 +// +#pragma once + +#include "TestHelpers.h" + +#include + +class FBXGeometry; + +class TestFbx : public GpuTestBase { + size_t _partCount { 0 }; + model::Material _material; + render::ShapeKey _shapeKey; + std::vector _partTransforms; + render::ShapePlumberPointer _shapePlumber; + gpu::Stream::FormatPointer _vertexFormat { std::make_shared() }; + gpu::BufferPointer _vertexBuffer { std::make_shared() }; + gpu::BufferPointer _indexBuffer { std::make_shared() }; + gpu::BufferPointer _indirectBuffer { std::make_shared() }; +public: + TestFbx(const render::ShapePlumberPointer& shapePlumber); + bool isReady() const override; + void renderTest(size_t test, RenderArgs* args) override; + +private: + void parseFbx(const QByteArray& fbxData); +}; + + diff --git a/tests/gpu-test/src/TestFloorGrid.cpp b/tests/gpu-test/src/TestFloorGrid.cpp new file mode 100644 index 0000000000..a24b4778d4 --- /dev/null +++ b/tests/gpu-test/src/TestFloorGrid.cpp @@ -0,0 +1,54 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 "TestFloorGrid.h" + + +TestFloorGrid::TestFloorGrid() { + auto geometryCache = DependencyManager::get(); + // Render grid on xz plane (not the optimal way to do things, but w/e) + // Note: GeometryCache::renderGrid will *not* work, as it is apparenly unaffected by batch rotations and renders xy only + static const std::string GRID_INSTANCE = "Grid"; + static auto compactColor1 = toCompactColor(vec4 { 0.35f, 0.25f, 0.15f, 1.0f }); + static auto compactColor2 = toCompactColor(vec4 { 0.15f, 0.25f, 0.35f, 1.0f }); + static std::vector transforms; + static gpu::BufferPointer colorBuffer; + if (!transforms.empty()) { + transforms.reserve(200); + colorBuffer = std::make_shared(); + for (int i = 0; i < 100; ++i) { + { + glm::mat4 transform = glm::translate(mat4(), vec3(0, -1, -50 + i)); + transform = glm::scale(transform, vec3(100, 1, 1)); + transforms.push_back(transform); + colorBuffer->append(compactColor1); + } + + { + glm::mat4 transform = glm::mat4_cast(quat(vec3(0, PI / 2.0f, 0))); + transform = glm::translate(transform, vec3(0, -1, -50 + i)); + transform = glm::scale(transform, vec3(100, 1, 1)); + transforms.push_back(transform); + colorBuffer->append(compactColor2); + } + } + } + +} + +void TestFloorGrid::renderTest(size_t testId, RenderArgs* args) { + //gpu::Batch& batch = *(args->_batch); + //auto pipeline = geometryCache->getSimplePipeline(); + //for (auto& transform : transforms) { + // batch.setModelTransform(transform); + // batch.setupNamedCalls(GRID_INSTANCE, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { + // batch.setPipeline(_pipeline); + // geometryCache->renderWireShapeInstances(batch, GeometryCache::Line, data.count(), colorBuffer); + // }); + //} +} diff --git a/tests/gpu-test/src/TestFloorGrid.h b/tests/gpu-test/src/TestFloorGrid.h new file mode 100644 index 0000000000..566c8f0968 --- /dev/null +++ b/tests/gpu-test/src/TestFloorGrid.h @@ -0,0 +1,26 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 +// + +#pragma once + +#include +#include +#include + +#include +#include + +#include "TestHelpers.h" + +class TestFloorGrid : public GpuTestBase { +public: + TestFloorGrid(); + void renderTest(size_t testId, RenderArgs* args) override; +}; + + diff --git a/tests/gpu-test/src/TestFloorTexture.cpp b/tests/gpu-test/src/TestFloorTexture.cpp new file mode 100644 index 0000000000..884be5ec30 --- /dev/null +++ b/tests/gpu-test/src/TestFloorTexture.cpp @@ -0,0 +1,88 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 "TestFloorTexture.h" + +struct Vertex { + vec4 position; + vec4 texture; + vec4 normal; + vec4 color; +}; +static const uint TEXTURE_OFFSET = offsetof(Vertex, texture); +static const uint NORMAL_OFFSET = offsetof(Vertex, normal); +static const uint POSITION_OFFSET = offsetof(Vertex, position); +static const uint COLOR_OFFSET = offsetof(Vertex, color); +static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; +static const gpu::Element TEXTURE_ELEMENT { gpu::VEC2, gpu::FLOAT, gpu::UV }; +static const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; +static const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::FLOAT, gpu::RGBA }; + +FloorTextureTest::FloorTextureTest() { + auto geometryCache = DependencyManager::get(); + std::vector vertices; + const int MINX = -1000; + const int MAXX = 1000; + + vertices.push_back({ + vec4(MAXX, 0, MAXX, 1), + vec4(MAXX, MAXX, 0, 0), + vec4(0, 1, 0, 1), + vec4(1), + }); + + vertices.push_back({ + vec4(MAXX, 0, MINX, 1), + vec4(MAXX, 0, 0, 0), + vec4(0, 1, 0, 1), + vec4(1), + }); + + vertices.push_back({ + vec4(MINX, 0, MINX, 1), + vec4(0, 0, 0, 0), + vec4(0, 1, 0, 1), + vec4(1), + }); + + vertices.push_back({ + vec4(MINX, 0, MAXX, 1), + vec4(0, MAXX, 0, 0), + vec4(0, 1, 0, 1), + vec4(1), + }); + + vertexBuffer->append(vertices); + indexBuffer->append(std::vector({ 0, 1, 2, 2, 3, 0 })); + texture = DependencyManager::get()->getImageTexture("C:/Users/bdavis/Git/openvr/samples/bin/cube_texture.png"); + //texture = DependencyManager::get()->getImageTexture("H:/test.png"); + //texture = DependencyManager::get()->getImageTexture("H:/crate_blue.fbm/lambert8SG_Normal_OpenGL.png"); + vertexFormat->setAttribute(gpu::Stream::POSITION, 0, POSITION_ELEMENT, POSITION_OFFSET); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD, 0, TEXTURE_ELEMENT, TEXTURE_OFFSET); + vertexFormat->setAttribute(gpu::Stream::COLOR, 0, COLOR_ELEMENT, COLOR_OFFSET); + vertexFormat->setAttribute(gpu::Stream::NORMAL, 0, NORMAL_ELEMENT, NORMAL_OFFSET); +} + +void FloorTextureTest::renderTest(size_t testId, RenderArgs* args) { + gpu::Batch& batch = *(args->_batch); + auto geometryCache = DependencyManager::get(); + static auto start = usecTimestampNow(); + auto now = usecTimestampNow(); + if ((now - start) > USECS_PER_SECOND * 1) { + start = now; + texture->incremementMinMip(); + } + + geometryCache->bindSimpleProgram(batch, true, true, true); + batch.setInputBuffer(0, vertexBuffer, 0, sizeof(Vertex)); + batch.setInputFormat(vertexFormat); + batch.setIndexBuffer(gpu::UINT16, indexBuffer, 0); + batch.setResourceTexture(0, texture); + batch.setModelTransform(glm::translate(glm::mat4(), vec3(0, -0.1, 0))); + batch.drawIndexed(gpu::TRIANGLES, 6, 0); +} diff --git a/tests/gpu-test/src/TestFloorTexture.h b/tests/gpu-test/src/TestFloorTexture.h new file mode 100644 index 0000000000..99eaf902b8 --- /dev/null +++ b/tests/gpu-test/src/TestFloorTexture.h @@ -0,0 +1,22 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 +// +#pragma once + +#include "TestHelpers.h" + +class FloorTextureTest : public GpuTestBase { + gpu::BufferPointer vertexBuffer { std::make_shared() }; + gpu::BufferPointer indexBuffer { std::make_shared() }; + gpu::Stream::FormatPointer vertexFormat { std::make_shared() }; + gpu::TexturePointer texture; +public: + FloorTextureTest(); + void renderTest(size_t testId, RenderArgs* args) override; +}; + + diff --git a/tests/gpu-test/src/TestHelpers.cpp b/tests/gpu-test/src/TestHelpers.cpp new file mode 100644 index 0000000000..75586da904 --- /dev/null +++ b/tests/gpu-test/src/TestHelpers.cpp @@ -0,0 +1,20 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 "TestHelpers.h" + +gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) { + 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); + } + return shader; +} diff --git a/tests/gpu-test/src/TestHelpers.h b/tests/gpu-test/src/TestHelpers.h new file mode 100644 index 0000000000..fd8989f628 --- /dev/null +++ b/tests/gpu-test/src/TestHelpers.h @@ -0,0 +1,33 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 +// + +#pragma once + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +class GpuTestBase { +public: + virtual ~GpuTestBase() {} + virtual bool isReady() const { return true; } + virtual size_t getTestCount() const { return 1; } + virtual void renderTest(size_t test, RenderArgs* args) = 0; +}; + +uint32_t toCompactColor(const glm::vec4& color); +gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings); + diff --git a/tests/gpu-test/src/TestInstancedShapes.cpp b/tests/gpu-test/src/TestInstancedShapes.cpp new file mode 100644 index 0000000000..6a98ee58b9 --- /dev/null +++ b/tests/gpu-test/src/TestInstancedShapes.cpp @@ -0,0 +1,84 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 "TestInstancedShapes.h" + +gpu::Stream::FormatPointer& getInstancedSolidStreamFormat(); + +static const size_t TYPE_COUNT = 4; +static const size_t ITEM_COUNT = 50; +static const float SHAPE_INTERVAL = (PI * 2.0f) / ITEM_COUNT; +static const float ITEM_INTERVAL = SHAPE_INTERVAL / TYPE_COUNT; + +static GeometryCache::Shape SHAPE[TYPE_COUNT] = { + GeometryCache::Icosahedron, + GeometryCache::Cube, + GeometryCache::Sphere, + GeometryCache::Tetrahedron, +}; + +const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; +const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; +const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::NUINT8, gpu::RGBA }; + + +TestInstancedShapes::TestInstancedShapes() { + auto geometryCache = DependencyManager::get(); + colorBuffer = std::make_shared(); + + static const float ITEM_RADIUS = 20; + static const vec3 ITEM_TRANSLATION { 0, 0, -ITEM_RADIUS }; + for (size_t i = 0; i < TYPE_COUNT; ++i) { + GeometryCache::Shape shape = SHAPE[i]; + GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; + //indirectCommand._count + float startingInterval = ITEM_INTERVAL * i; + std::vector typeTransforms; + for (size_t j = 0; j < ITEM_COUNT; ++j) { + float theta = j * SHAPE_INTERVAL + startingInterval; + auto transform = glm::rotate(mat4(), theta, Vectors::UP); + transform = glm::rotate(transform, (randFloat() - 0.5f) * PI / 4.0f, Vectors::UNIT_X); + transform = glm::translate(transform, ITEM_TRANSLATION); + transform = glm::scale(transform, vec3(randFloat() / 2.0f + 0.5f)); + typeTransforms.push_back(transform); + auto color = vec4 { randomColorValue(64), randomColorValue(64), randomColorValue(64), 255 }; + color /= 255.0f; + colors.push_back(color); + colorBuffer->append(toCompactColor(color)); + } + transforms.push_back(typeTransforms); + } +} + +void TestInstancedShapes::renderTest(size_t testId, RenderArgs* args) { + gpu::Batch& batch = *(args->_batch); + auto geometryCache = DependencyManager::get(); + geometryCache->bindSimpleProgram(batch); + batch.setInputFormat(getInstancedSolidStreamFormat()); + for (size_t i = 0; i < TYPE_COUNT; ++i) { + GeometryCache::Shape shape = SHAPE[i]; + GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; + + std::string namedCall = __FUNCTION__ + std::to_string(i); + + //batch.addInstanceModelTransforms(transforms[i]); + for (size_t j = 0; j < ITEM_COUNT; ++j) { + batch.setModelTransform(transforms[i][j]); + batch.setupNamedCalls(namedCall, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData&) { + batch.setInputBuffer(gpu::Stream::COLOR, gpu::BufferView(colorBuffer, i * ITEM_COUNT * 4, colorBuffer->getSize(), COLOR_ELEMENT)); + shapeData.drawInstances(batch, ITEM_COUNT); + }); + } + + //for (size_t j = 0; j < ITEM_COUNT; ++j) { + // batch.setModelTransform(transforms[j + i * ITEM_COUNT]); + // shapeData.draw(batch); + //} + } +} + diff --git a/tests/gpu-test/src/TestInstancedShapes.h b/tests/gpu-test/src/TestInstancedShapes.h new file mode 100644 index 0000000000..b509a13e60 --- /dev/null +++ b/tests/gpu-test/src/TestInstancedShapes.h @@ -0,0 +1,23 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 +// + +#pragma once + +#include "TestHelpers.h" + +class TestInstancedShapes : public GpuTestBase { + + std::vector> transforms; + std::vector colors; + gpu::BufferPointer colorBuffer; + gpu::BufferView instanceXfmView; +public: + TestInstancedShapes(); + void renderTest(size_t testId, RenderArgs* args) override; +}; + diff --git a/tests/gpu-test/src/TestShapes.cpp b/tests/gpu-test/src/TestShapes.cpp new file mode 100644 index 0000000000..253d89cf61 --- /dev/null +++ b/tests/gpu-test/src/TestShapes.cpp @@ -0,0 +1,48 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 "TestShapes.h" + +static const size_t TYPE_COUNT = 6; + +static GeometryCache::Shape SHAPE[TYPE_COUNT] = { + GeometryCache::Cube, + GeometryCache::Tetrahedron, + GeometryCache::Octahedron, + GeometryCache::Dodecahedron, + GeometryCache::Icosahedron, + GeometryCache::Sphere, +}; + +void TestShapes::renderTest(size_t testId, RenderArgs* args) { + gpu::Batch& batch = *(args->_batch); + auto geometryCache = DependencyManager::get(); + geometryCache->bindSimpleProgram(batch); + + // Render unlit cube + sphere + static auto startSecs = secTimestampNow(); + float seconds = secTimestampNow() - startSecs; + seconds /= 4.0f; + batch.setModelTransform(Transform()); + batch._glColor4f(0.8f, 0.25f, 0.25f, 1.0f); + + bool wire = (seconds - floorf(seconds) > 0.5f); + int shapeIndex = ((int)seconds) % TYPE_COUNT; + if (wire) { + geometryCache->renderWireShape(batch, SHAPE[shapeIndex]); + } else { + geometryCache->renderShape(batch, SHAPE[shapeIndex]); + } + + batch.setModelTransform(Transform().setScale(1.01f)); + batch._glColor4f(1, 1, 1, 1); + geometryCache->renderWireCube(batch); +} + + + diff --git a/tests/gpu-test/src/TestShapes.h b/tests/gpu-test/src/TestShapes.h new file mode 100644 index 0000000000..606d3a45f7 --- /dev/null +++ b/tests/gpu-test/src/TestShapes.h @@ -0,0 +1,22 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 +// + +#pragma once + +#include "TestHelpers.h" + +class TestShapes : public GpuTestBase { + + std::vector> transforms; + std::vector colors; + gpu::BufferPointer colorBuffer; + gpu::BufferView instanceXfmView; +public: + void renderTest(size_t testId, RenderArgs* args) override; +}; + diff --git a/tests/gpu-test/src/TestWindow.cpp b/tests/gpu-test/src/TestWindow.cpp new file mode 100644 index 0000000000..f31eb6f7bb --- /dev/null +++ b/tests/gpu-test/src/TestWindow.cpp @@ -0,0 +1,197 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 "TestWindow.h" + +#include +#include + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#ifdef DEFERRED_LIGHTING +extern void initDeferredPipelines(render::ShapePlumber& plumber); +extern void initStencilPipeline(gpu::PipelinePointer& pipeline); +#endif + +TestWindow::TestWindow() { + setSurfaceType(QSurface::OpenGLSurface); + + + auto timer = new QTimer(this); + timer->setInterval(5); + connect(timer, &QTimer::timeout, [&] { draw(); }); + timer->start(); + + connect(qApp, &QCoreApplication::aboutToQuit, [this, timer] { + timer->stop(); + _aboutToQuit = true; + }); + +#ifdef DEFERRED_LIGHTING + _light->setType(model::Light::SUN); + _light->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset::OLD_TOWN_SQUARE); + _light->setIntensity(1.0f); + _light->setAmbientIntensity(0.5f); + _light->setColor(vec3(1.0f)); + _light->setPosition(vec3(1, 1, 1)); + _renderContext->args = _renderArgs; +#endif + + QSurfaceFormat format = getDefaultOpenGLSurfaceFormat(); + format.setOption(QSurfaceFormat::DebugContext); + //format.setSwapInterval(0); + setFormat(format); + _glContext.setFormat(format); + _glContext.create(); + _glContext.makeCurrent(this); + show(); +} + +void TestWindow::initGl() { + _glContext.makeCurrent(this); + gpu::Context::init(); + _renderArgs->_context = std::make_shared(); + _glContext.makeCurrent(this); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + resize(QSize(800, 600)); + + setupDebugLogger(this); +#ifdef DEFERRED_LIGHTING + auto deferredLightingEffect = DependencyManager::get(); + deferredLightingEffect->init(); + deferredLightingEffect->setGlobalLight(_light); + initDeferredPipelines(*_shapePlumber); +#endif +} + +void TestWindow::resizeWindow(const QSize& size) { + _size = size; + _renderArgs->_viewport = ivec4(0, 0, _size.width(), _size.height()); + auto fboCache = DependencyManager::get(); + if (fboCache) { + fboCache->setFrameBufferSize(_size); + } +} + +void TestWindow::beginFrame() { + _renderArgs->_context->syncCache(); + +#ifdef DEFERRED_LIGHTING + + gpu::FramebufferPointer primaryFramebuffer; + _preparePrimaryFramebuffer.run(_sceneContext, _renderContext, primaryFramebuffer); + + DeferredFrameTransformPointer frameTransform; + _generateDeferredFrameTransform.run(_sceneContext, _renderContext, frameTransform); + + LightingModelPointer lightingModel; + _generateLightingModel.run(_sceneContext, _renderContext, lightingModel); + + _prepareDeferredInputs.edit0() = primaryFramebuffer; + _prepareDeferredInputs.edit1() = lightingModel; + _prepareDeferred.run(_sceneContext, _renderContext, _prepareDeferredInputs, _prepareDeferredOutputs); + + + _renderDeferredInputs.edit0() = frameTransform; // Pass the deferredFrameTransform + _renderDeferredInputs.edit1() = _prepareDeferredOutputs.get0(); // Pass the deferredFramebuffer + _renderDeferredInputs.edit2() = lightingModel; // Pass the lightingModel + // the rest of the renderDeferred inputs can be omitted + +#else + gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.1f, 0.2f, 1.0f }); + batch.clearDepthFramebuffer(1e4); + batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); + }); +#endif + + gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + batch.setViewportTransform(_renderArgs->_viewport); + batch.setStateScissorRect(_renderArgs->_viewport); + batch.setProjectionTransform(_projectionMatrix); + }); +} + +void TestWindow::endFrame() { +#ifdef DEFERRED_LIGHTING + RenderArgs* args = _renderContext->args; + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + auto deferredFboColorDepthStencil = _prepareDeferredOutputs.get0()->getDeferredFramebufferDepthColor(); + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + batch.setFramebuffer(deferredFboColorDepthStencil); + batch.setPipeline(_opaquePipeline); + batch.draw(gpu::TRIANGLE_STRIP, 4); + batch.setResourceTexture(0, nullptr); + }); + + _renderDeferred.run(_sceneContext, _renderContext, _renderDeferredInputs); + + gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + PROFILE_RANGE_BATCH(batch, "blit"); + // Blit to screen + auto framebufferCache = DependencyManager::get(); + // auto framebuffer = framebufferCache->getLightingFramebuffer(); + auto framebuffer = _prepareDeferredOutputs.get0()->getLightingFramebuffer(); + batch.blit(framebuffer, _renderArgs->_viewport, nullptr, _renderArgs->_viewport); + }); +#endif + + gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + batch.resetStages(); + }); + _glContext.swapBuffers(this); +} + +void TestWindow::draw() { + if (_aboutToQuit) { + return; + } + + // Attempting to draw before we're visible and have a valid size will + // produce GL errors. + if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) { + return; + } + + if (!_glContext.makeCurrent(this)) { + return; + } + + static std::once_flag once; + std::call_once(once, [&] { initGl(); }); + beginFrame(); + + renderFrame(); + + endFrame(); +} + +void TestWindow::resizeEvent(QResizeEvent* ev) { + resizeWindow(ev->size()); + float fov_degrees = 60.0f; + float aspect_ratio = (float)_size.width() / _size.height(); + float near_clip = 0.1f; + float far_clip = 1000.0f; + _projectionMatrix = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); +} diff --git a/tests/gpu-test/src/TestWindow.h b/tests/gpu-test/src/TestWindow.h new file mode 100644 index 0000000000..c8d09825aa --- /dev/null +++ b/tests/gpu-test/src/TestWindow.h @@ -0,0 +1,66 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// 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 +// + +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#define DEFERRED_LIGHTING + +class TestWindow : public QWindow { +protected: + QOpenGLContextWrapper _glContext; + QSize _size; + glm::mat4 _projectionMatrix; + bool _aboutToQuit { false }; + +#ifdef DEFERRED_LIGHTING + // Prepare the ShapePipelines + render::ShapePlumberPointer _shapePlumber { std::make_shared() }; + render::SceneContextPointer _sceneContext{ std::make_shared() }; + render::RenderContextPointer _renderContext{ std::make_shared() }; + gpu::PipelinePointer _opaquePipeline; + model::LightPointer _light { std::make_shared() }; + + GenerateDeferredFrameTransform _generateDeferredFrameTransform; + MakeLightingModel _generateLightingModel; + PreparePrimaryFramebuffer _preparePrimaryFramebuffer; + + PrepareDeferred::Inputs _prepareDeferredInputs; + PrepareDeferred _prepareDeferred; + PrepareDeferred::Outputs _prepareDeferredOutputs; + + RenderDeferred::Inputs _renderDeferredInputs; + RenderDeferred _renderDeferred; + +#endif + + RenderArgs* _renderArgs { new RenderArgs() }; + + TestWindow(); + virtual void initGl(); + virtual void renderFrame() = 0; + +private: + void resizeWindow(const QSize& size); + + void beginFrame(); + void endFrame(); + void draw(); + void resizeEvent(QResizeEvent* ev) override; +}; + diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index b80539b33a..0f06546327 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -42,238 +42,67 @@ #include #include +#include #include +#include +#include +#include +#include -#include "unlit_frag.h" -#include "unlit_vert.h" +#include +#include -class RateCounter { - std::vector times; - QElapsedTimer timer; -public: - RateCounter() { - timer.start(); - } +#include +#include +#include +#include +#include +#include +#include +#include - void reset() { - times.clear(); - } +#include "TestWindow.h" +#include "TestFbx.h" +#include "TestFloorGrid.h" +#include "TestFloorTexture.h" +#include "TestInstancedShapes.h" +#include "TestShapes.h" - unsigned int count() const { - return (unsigned int)times.size() - 1; - } - - float elapsed() const { - if (times.size() < 1) { - return 0.0f; - } - float elapsed = *times.rbegin() - *times.begin(); - return elapsed; - } - - void increment() { - times.push_back(timer.elapsed() / 1000.0f); - } - - float rate() const { - if (elapsed() == 0.0f) { - return NAN; - } - return (float) count() / elapsed(); - } -}; - -uint32_t toCompactColor(const glm::vec4& color); +using namespace render; -const char* VERTEX_SHADER = R"SHADER( - -layout(location = 0) in vec4 inPosition; -layout(location = 3) in vec2 inTexCoord0; - -struct TransformObject { - mat4 _model; - mat4 _modelInverse; -}; - -layout(location=15) in ivec2 _drawCallInfo; - -uniform samplerBuffer transformObjectBuffer; - -TransformObject getTransformObject() { - int offset = 8 * _drawCallInfo.x; - TransformObject object; - object._model[0] = texelFetch(transformObjectBuffer, offset); - object._model[1] = texelFetch(transformObjectBuffer, offset + 1); - object._model[2] = texelFetch(transformObjectBuffer, offset + 2); - object._model[3] = texelFetch(transformObjectBuffer, offset + 3); - - object._modelInverse[0] = texelFetch(transformObjectBuffer, offset + 4); - object._modelInverse[1] = texelFetch(transformObjectBuffer, offset + 5); - object._modelInverse[2] = texelFetch(transformObjectBuffer, offset + 6); - object._modelInverse[3] = texelFetch(transformObjectBuffer, offset + 7); - - return object; -} - -struct TransformCamera { - mat4 _view; - mat4 _viewInverse; - mat4 _projectionViewUntranslated; - mat4 _projection; - mat4 _projectionInverse; - vec4 _viewport; -}; - -layout(std140) uniform transformCameraBuffer { - TransformCamera _camera; -}; - -TransformCamera getTransformCamera() { - return _camera; -} - -// the interpolated normal -out vec2 _texCoord0; - -void main(void) { - _texCoord0 = inTexCoord0.st; - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - { // transformModelToClipPos - vec4 eyeWAPos; - { // _transformModelToEyeWorldAlignedPos - highp mat4 _mv = obj._model; - _mv[3].xyz -= cam._viewInverse[3].xyz; - highp vec4 _eyeWApos = (_mv * inPosition); - eyeWAPos = _eyeWApos; - } - gl_Position = cam._projectionViewUntranslated * eyeWAPos; - } - -})SHADER"; - -const char* FRAGMENT_SHADER = R"SHADER( - -uniform sampler2D originalTexture; - -in vec2 _texCoord0; - -layout(location = 0) out vec4 _fragColor0; - -void main(void) { - //_fragColor0 = vec4(_texCoord0, 0.0, 1.0); - _fragColor0 = texture(originalTexture, _texCoord0); -} -)SHADER"; +using TestBuilder = std::function; +using TestBuilders = std::list; -gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) { - 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); - } - return shader; -} +#define INTERACTIVE -float getSeconds(quint64 start = 0) { - auto usecs = usecTimestampNow() - start; - auto msecs = usecs / USECS_PER_MSEC; - float seconds = (float)msecs / MSECS_PER_SECOND; - return seconds; -} - -static const size_t TYPE_COUNT = 4; -static GeometryCache::Shape SHAPE[TYPE_COUNT] = { - GeometryCache::Icosahedron, - GeometryCache::Cube, - GeometryCache::Sphere, - GeometryCache::Tetrahedron, - //GeometryCache::Line, -}; - -gpu::Stream::FormatPointer& getInstancedSolidStreamFormat(); - -// Creates an OpenGL window that renders a simple unlit scene using the gpu library and GeometryCache -// Should eventually get refactored into something that supports multiple gpu backends. -class QTestWindow : public QWindow { - Q_OBJECT - - QOpenGLContextWrapper _qGlContext; - QSize _size; - - gpu::ContextPointer _context; - gpu::PipelinePointer _pipeline; - glm::mat4 _projectionMatrix; - RateCounter fps; - QTime _time; +class MyTestWindow : public TestWindow { + using Parent = TestWindow; + TestBuilders _testBuilders; + GpuTestBase* _currentTest { nullptr }; + size_t _currentTestId { 0 }; + size_t _currentMaxTests { 0 }; glm::mat4 _camera; + QTime _time; -protected: - void renderText(); - -private: - void resizeWindow(const QSize& size) { - _size = size; - } - -public: - QTestWindow() { - setSurfaceType(QSurface::OpenGLSurface); - - QSurfaceFormat format; - // Qt Quick may need a depth and stencil buffer. Always make sure these are available. - format.setDepthBufferSize(16); - format.setStencilBufferSize(8); - setGLFormatVersion(format); - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); - format.setOption(QSurfaceFormat::DebugContext); - //format.setSwapInterval(0); - - setFormat(format); - - _qGlContext.setFormat(format); - _qGlContext.create(); - - show(); - makeCurrent(); - setupDebugLogger(this); - - gpu::Context::init(); - _context = std::make_shared(); - makeCurrent(); - auto shader = makeShader(unlit_vert, unlit_frag, gpu::Shader::BindingSet{}); - auto state = std::make_shared(); - state->setMultisampleEnable(true); - state->setDepthTest(gpu::State::DepthTest { true }); - _pipeline = gpu::Pipeline::create(shader, state); - - - - // Clear screen - gpu::Batch batch; - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, 0.0, 0.5, 1.0 }); - _context->render(batch); - - DependencyManager::set(); - DependencyManager::set(); - DependencyManager::set(); - - resize(QSize(800, 600)); - + void initGl() override { + Parent::initGl(); +#ifdef INTERACTIVE _time.start(); - } - - virtual ~QTestWindow() { +#endif + updateCamera(); + _testBuilders = TestBuilders({ + [this] { return new TestFbx(_shapePlumber); }, + [] { return new TestInstancedShapes(); }, + }); } void updateCamera() { - float t = _time.elapsed() * 1e-4f; + float t = 0; +#ifdef INTERACTIVE + t = _time.elapsed() * 1e-3f; +#endif glm::vec3 unitscale { 1.0f }; glm::vec3 up { 0.0f, 1.0f, 0.0f }; @@ -283,263 +112,64 @@ public: static const vec3 camera_focus(0); static const vec3 camera_up(0, 1, 0); _camera = glm::inverse(glm::lookAt(camera_position, camera_focus, up)); + + ViewFrustum frustum; + frustum.setPosition(camera_position); + frustum.setOrientation(glm::quat_cast(_camera)); + frustum.setProjection(_projectionMatrix); + _renderArgs->setViewFrustum(frustum); } + void renderFrame() override { + updateCamera(); - void drawFloorGrid(gpu::Batch& batch) { - auto geometryCache = DependencyManager::get(); - // Render grid on xz plane (not the optimal way to do things, but w/e) - // Note: GeometryCache::renderGrid will *not* work, as it is apparenly unaffected by batch rotations and renders xy only - static const std::string GRID_INSTANCE = "Grid"; - static auto compactColor1 = toCompactColor(vec4 { 0.35f, 0.25f, 0.15f, 1.0f }); - static auto compactColor2 = toCompactColor(vec4 { 0.15f, 0.25f, 0.35f, 1.0f }); - static std::vector transforms; - static gpu::BufferPointer colorBuffer; - if (!transforms.empty()) { - transforms.reserve(200); - colorBuffer = std::make_shared(); - for (int i = 0; i < 100; ++i) { - { - glm::mat4 transform = glm::translate(mat4(), vec3(0, -1, -50 + i)); - transform = glm::scale(transform, vec3(100, 1, 1)); - transforms.push_back(transform); - colorBuffer->append(compactColor1); - } + while ((!_currentTest || (_currentTestId >= _currentMaxTests)) && !_testBuilders.empty()) { + if (_currentTest) { + delete _currentTest; + _currentTest = nullptr; + } - { - glm::mat4 transform = glm::mat4_cast(quat(vec3(0, PI / 2.0f, 0))); - transform = glm::translate(transform, vec3(0, -1, -50 + i)); - transform = glm::scale(transform, vec3(100, 1, 1)); - transforms.push_back(transform); - colorBuffer->append(compactColor2); - } + _currentTest = _testBuilders.front()(); + _testBuilders.pop_front(); + + if (_currentTest) { + _currentMaxTests = _currentTest->getTestCount(); + _currentTestId = 0; } } - auto pipeline = geometryCache->getSimplePipeline(); - for (auto& transform : transforms) { - batch.setModelTransform(transform); - batch.setupNamedCalls(GRID_INSTANCE, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { - batch.setViewTransform(_camera); - batch.setPipeline(_pipeline); - geometryCache->renderWireShapeInstances(batch, GeometryCache::Line, data.count(), colorBuffer); - }); - } - } - void drawSimpleShapes(gpu::Batch& batch) { - auto geometryCache = DependencyManager::get(); - static const size_t ITEM_COUNT = 1000; - static const float SHAPE_INTERVAL = (PI * 2.0f) / ITEM_COUNT; - static const float ITEM_INTERVAL = SHAPE_INTERVAL / TYPE_COUNT; - - static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; - static const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; - static const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::NUINT8, gpu::RGBA }; - - static std::vector transforms; - static std::vector colors; - static gpu::BufferPointer colorBuffer; - static gpu::BufferView colorView; - static gpu::BufferView instanceXfmView; - if (!colorBuffer) { - colorBuffer = std::make_shared(); - - static const float ITEM_RADIUS = 20; - static const vec3 ITEM_TRANSLATION { 0, 0, -ITEM_RADIUS }; - for (size_t i = 0; i < TYPE_COUNT; ++i) { - GeometryCache::Shape shape = SHAPE[i]; - GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; - //indirectCommand._count - float startingInterval = ITEM_INTERVAL * i; - for (size_t j = 0; j < ITEM_COUNT; ++j) { - float theta = j * SHAPE_INTERVAL + startingInterval; - auto transform = glm::rotate(mat4(), theta, Vectors::UP); - transform = glm::rotate(transform, (randFloat() - 0.5f) * PI / 4.0f, Vectors::UNIT_X); - transform = glm::translate(transform, ITEM_TRANSLATION); - transform = glm::scale(transform, vec3(randFloat() / 2.0f + 0.5f)); - transforms.push_back(transform); - auto color = vec4 { randomColorValue(64), randomColorValue(64), randomColorValue(64), 255 }; - color /= 255.0f; - colors.push_back(color); - colorBuffer->append(toCompactColor(color)); - } - } - colorView = gpu::BufferView(colorBuffer, COLOR_ELEMENT); - } - - batch.setViewTransform(_camera); - batch.setPipeline(_pipeline); - batch.setInputFormat(getInstancedSolidStreamFormat()); - for (size_t i = 0; i < TYPE_COUNT; ++i) { - GeometryCache::Shape shape = SHAPE[i]; - GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; - batch.setInputBuffer(gpu::Stream::COLOR, colorView); - for (size_t j = 0; j < ITEM_COUNT; ++j) { - batch.setModelTransform(transforms[j]); - shapeData.draw(batch); - } - } - } - - void drawCenterShape(gpu::Batch& batch) { - // Render unlit cube + sphere - static auto startUsecs = usecTimestampNow(); - float seconds = getSeconds(startUsecs); - seconds /= 4.0f; - batch.setModelTransform(Transform()); - batch._glColor4f(0.8f, 0.25f, 0.25f, 1.0f); - - bool wire = (seconds - floorf(seconds) > 0.5f); - auto geometryCache = DependencyManager::get(); - int shapeIndex = ((int)seconds) % TYPE_COUNT; - if (wire) { - geometryCache->renderWireShape(batch, SHAPE[shapeIndex]); - } else { - geometryCache->renderShape(batch, SHAPE[shapeIndex]); - } - - batch.setModelTransform(Transform().setScale(2.05f)); - batch._glColor4f(1, 1, 1, 1); - geometryCache->renderWireCube(batch); - } - - void drawTerrain(gpu::Batch& batch) { - auto geometryCache = DependencyManager::get(); - static std::once_flag once; - static gpu::BufferPointer vertexBuffer { std::make_shared() }; - static gpu::BufferPointer indexBuffer { std::make_shared() }; - - static gpu::BufferView positionView; - static gpu::BufferView textureView; - static gpu::Stream::FormatPointer vertexFormat { std::make_shared() }; - - static gpu::TexturePointer texture; - static gpu::PipelinePointer pipeline; - std::call_once(once, [&] { - static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec4) * 2; // position, normals, textures - static const uint SHAPE_TEXTURES_OFFSET = sizeof(glm::vec4); - static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; - static const gpu::Element TEXTURE_ELEMENT { gpu::VEC2, gpu::FLOAT, gpu::UV }; - std::vector vertices; - const int MINX = -1000; - const int MAXX = 1000; - - // top - vertices.push_back(vec4(MAXX, 0, MAXX, 1)); - vertices.push_back(vec4(MAXX, MAXX, 0, 0)); - - vertices.push_back(vec4(MAXX, 0, MINX, 1)); - vertices.push_back(vec4(MAXX, 0, 0, 0)); - - vertices.push_back(vec4(MINX, 0, MINX, 1)); - vertices.push_back(vec4(0, 0, 0, 0)); - - vertices.push_back(vec4(MINX, 0, MAXX, 1)); - vertices.push_back(vec4(0, MAXX, 0, 0)); - - vertexBuffer->append(vertices); - indexBuffer->append(std::vector({ 0, 1, 2, 2, 3, 0 })); - - positionView = gpu::BufferView(vertexBuffer, 0, vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, POSITION_ELEMENT); - textureView = gpu::BufferView(vertexBuffer, SHAPE_TEXTURES_OFFSET, vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, TEXTURE_ELEMENT); - texture = DependencyManager::get()->getImageTexture("C:/Users/bdavis/Git/openvr/samples/bin/cube_texture.png"); - // texture = DependencyManager::get()->getImageTexture("H:/test.png"); - //texture = DependencyManager::get()->getImageTexture("H:/crate_blue.fbm/lambert8SG_Normal_OpenGL.png"); - - auto shader = makeShader(VERTEX_SHADER, FRAGMENT_SHADER, gpu::Shader::BindingSet {}); - auto state = std::make_shared(); - state->setMultisampleEnable(false); - state->setDepthTest(gpu::State::DepthTest { true }); - pipeline = gpu::Pipeline::create(shader, state); - vertexFormat->setAttribute(gpu::Stream::POSITION); - vertexFormat->setAttribute(gpu::Stream::TEXCOORD); - }); - - static auto start = usecTimestampNow(); - auto now = usecTimestampNow(); - if ((now - start) > USECS_PER_SECOND * 1) { - start = now; - texture->incremementMinMip(); - } - - batch.setPipeline(pipeline); - batch.setInputBuffer(gpu::Stream::POSITION, positionView); - batch.setInputBuffer(gpu::Stream::TEXCOORD, textureView); - batch.setIndexBuffer(gpu::UINT16, indexBuffer, 0); - batch.setInputFormat(vertexFormat); - - batch.setResourceTexture(0, texture); - batch.setModelTransform(glm::translate(glm::mat4(), vec3(0, -0.1, 0))); - batch.drawIndexed(gpu::TRIANGLES, 6, 0); - - batch.setResourceTexture(0, DependencyManager::get()->getBlueTexture()); - batch.setModelTransform(glm::translate(glm::mat4(), vec3(0, -0.2, 0))); - batch.drawIndexed(gpu::TRIANGLES, 6, 0); - } - - void draw() { - // Attempting to draw before we're visible and have a valid size will - // produce GL errors. - if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) { + if (!_currentTest && _testBuilders.empty()) { + qApp->quit(); return; } - updateCamera(); - makeCurrent(); - - gpu::Batch batch; - batch.resetStages(); - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.1f, 0.2f, 1.0f }); - batch.clearDepthFramebuffer(1e4); - batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); - batch.setProjectionTransform(_projectionMatrix); - - batch.setViewTransform(_camera); - batch.setPipeline(_pipeline); - batch.setModelTransform(Transform()); - //drawFloorGrid(batch); - //drawSimpleShapes(batch); - //drawCenterShape(batch); - drawTerrain(batch); - - _context->render(batch); - _qGlContext.swapBuffers(this); - - fps.increment(); - if (fps.elapsed() >= 0.5f) { - qDebug() << "FPS: " << fps.rate(); - fps.reset(); + // Tests might need to wait for resources to download + if (!_currentTest->isReady()) { + return; } - } - - void makeCurrent() { - _qGlContext.makeCurrent(this); - } -protected: - void resizeEvent(QResizeEvent* ev) override { - resizeWindow(ev->size()); - - float fov_degrees = 60.0f; - float aspect_ratio = (float)_size.width() / _size.height(); - float near_clip = 0.1f; - float far_clip = 1000.0f; - _projectionMatrix = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); - } + gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + batch.setViewTransform(_camera); + _renderArgs->_batch = &batch; + _currentTest->renderTest(_currentTestId, _renderArgs); + _renderArgs->_batch = nullptr; + }); + +#ifdef INTERACTIVE + +#else + // TODO Capture the current rendered framebuffer and save + // Increment the test ID + ++_currentTestId; +#endif + } }; + int main(int argc, char** argv) { QGuiApplication app(argc, argv); - QTestWindow window; - auto timer = new QTimer(&app); - timer->setInterval(0); - app.connect(timer, &QTimer::timeout, &app, [&] { - window.draw(); - }); - timer->start(); + MyTestWindow window; app.exec(); return 0; } -#include "main.moc" - diff --git a/tests/gpu-test/src/unlit.slf b/tests/gpu-test/src/unlit.slf deleted file mode 100644 index f88fcb510b..0000000000 --- a/tests/gpu-test/src/unlit.slf +++ /dev/null @@ -1,28 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// simple.frag -// fragment shader -// -// Created by Andrzej Kapolka on 9/15/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 DeferredBufferWrite.slh@> -<@include model/Material.slh@> - -// the interpolated normal -in vec3 _normal; -in vec3 _color; - -void main(void) { - packDeferredFragment( - normalize(_normal.xyz), - 1.0, - _color.rgb, - DEFAULT_ROUGHNESS, DEFAULT_METALLIC, DEFAULT_EMISSIVE, DEFAULT_OCCLUSION); -} diff --git a/tests/physics/src/ShapeManagerTests.cpp b/tests/physics/src/ShapeManagerTests.cpp index 66ac9d0c4a..c8805132fa 100644 --- a/tests/physics/src/ShapeManagerTests.cpp +++ b/tests/physics/src/ShapeManagerTests.cpp @@ -194,23 +194,23 @@ void ShapeManagerTests::addCompoundShape() { int numHullPoints = tetrahedron.size(); // compute the points of the hulls - QVector< QVector > hulls; + ShapeInfo::PointCollection pointCollection; int numHulls = 5; glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f); for (int i = 0; i < numHulls; ++i) { glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal; - QVector hull; + ShapeInfo::PointList pointList; float radius = (float)(i + 1); for (int j = 0; j < numHullPoints; ++j) { glm::vec3 point = radius * tetrahedron[j] + offset; - hull.push_back(point); + pointList.push_back(point); } - hulls.push_back(hull); + pointCollection.push_back(pointList); } // create the ShapeInfo ShapeInfo info; - info.setConvexHulls(hulls); + info.setPointCollection(hulls); // create the shape ShapeManager shapeManager; diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt new file mode 100644 index 0000000000..d4f90fdace --- /dev/null +++ b/tests/render-perf/CMakeLists.txt @@ -0,0 +1,18 @@ + +set(TARGET_NAME render-perf-test) + +if (WIN32) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") +endif() + +# This is not a testcase -- just set it up as a regular hifi project +setup_hifi_project(Quick Gui OpenGL) +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") + +# link in the shared libraries +link_hifi_libraries(shared octree gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics) + +package_libraries_for_deployment() + + +target_bullet() diff --git a/tests/render-perf/src/Camera.hpp b/tests/render-perf/src/Camera.hpp new file mode 100644 index 0000000000..fbae04540b --- /dev/null +++ b/tests/render-perf/src/Camera.hpp @@ -0,0 +1,138 @@ +#pragma once + +#include + +class Camera { +protected: + float fov { 60.0f }; + float znear { DEFAULT_NEAR_CLIP }, zfar { DEFAULT_FAR_CLIP }; + float aspect { 1.0f }; + + void updateViewMatrix() { + matrices.view = glm::inverse(glm::translate(glm::mat4(), position) * glm::mat4_cast(getOrientation())); + } + +public: + glm::quat getOrientation() const { + return glm::angleAxis(yaw, Vectors::UP); + } + float yaw { 0 }; + glm::vec3 position; + float rotationSpeed { 1.0f }; + float movementSpeed { 1.0f }; + + struct Matrices { + glm::mat4 perspective; + glm::mat4 view; + } matrices; + enum Key { + RIGHT, + LEFT, + UP, + DOWN, + BACK, + FORWARD, + KEYS_SIZE, + INVALID = -1, + }; + + std::bitset keys; + + Camera() { + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + } + + bool moving() { + return keys.any(); + } + + void setFieldOfView(float fov) { + this->fov = fov; + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + } + + void setAspectRatio(const glm::vec2& size) { + setAspectRatio(size.x / size.y); + } + + void setAspectRatio(float aspect) { + this->aspect = aspect; + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + } + + void setPerspective(float fov, const glm::vec2& size, float znear = 0.1f, float zfar = 512.0f) { + setPerspective(fov, size.x / size.y, znear, zfar); + } + + void setPerspective(float fov, float aspect, float znear = 0.1f, float zfar = 512.0f) { + this->aspect = aspect; + this->fov = fov; + this->znear = znear; + this->zfar = zfar; + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + }; + + void rotate(const float delta) { + yaw += delta; + updateViewMatrix(); + } + + void setRotation(const glm::quat& rotation) { + glm::vec3 f = rotation * Vectors::UNIT_NEG_Z; + f.y = 0; + f = glm::normalize(f); + yaw = angleBetween(Vectors::UNIT_NEG_Z, f); + updateViewMatrix(); + } + + void setPosition(const glm::vec3& position) { + this->position = position; + updateViewMatrix(); + } + + // Translate in the Z axis of the camera + void dolly(float delta) { + auto direction = glm::vec3(0, 0, delta); + translate(direction); + } + + // Translate in the XY plane of the camera + void translate(const glm::vec2& delta) { + auto move = glm::vec3(delta.x, delta.y, 0); + translate(move); + } + + void translate(const glm::vec3& delta) { + position += getOrientation() * delta; + updateViewMatrix(); + } + + void update(float deltaTime) { + if (moving()) { + glm::vec3 camFront = getOrientation() * Vectors::FRONT; + glm::vec3 camRight = getOrientation() * Vectors::RIGHT; + glm::vec3 camUp = getOrientation() * Vectors::UP; + float moveSpeed = deltaTime * movementSpeed; + + if (keys[FORWARD]) { + position += camFront * moveSpeed; + } + if (keys[BACK]) { + position -= camFront * moveSpeed; + } + if (keys[LEFT]) { + position -= camRight * moveSpeed; + } + if (keys[RIGHT]) { + position += camRight * moveSpeed; + } + if (keys[UP]) { + position += camUp * moveSpeed; + } + if (keys[DOWN]) { + position -= camUp * moveSpeed; + } + updateViewMatrix(); + } + } +}; diff --git a/tests/render-perf/src/TextOverlay.hpp b/tests/render-perf/src/TextOverlay.hpp new file mode 100644 index 0000000000..d9a9f0a320 --- /dev/null +++ b/tests/render-perf/src/TextOverlay.hpp @@ -0,0 +1,239 @@ +/* +* Text overlay class for displaying debug information +* +* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +#include "stb_font_consolas_24_latin1.inl" + +// Defines for the STB font used +// STB font files can be found at http://nothings.org/stb/font/ +#define STB_FONT_NAME stb_font_consolas_24_latin1 +#define STB_FONT_WIDTH STB_FONT_consolas_24_latin1_BITMAP_WIDTH +#define STB_FONT_HEIGHT STB_FONT_consolas_24_latin1_BITMAP_HEIGHT +#define STB_FIRST_CHAR STB_FONT_consolas_24_latin1_FIRST_CHAR +#define STB_NUM_CHARS STB_FONT_consolas_24_latin1_NUM_CHARS + +// Max. number of chars the text overlay buffer can hold +#define MAX_CHAR_COUNT 1024 + +// Mostly self-contained text overlay class +// todo : comment +class TextOverlay { +private: + FramebufferPtr _framebuffer; + TexturePtr _texture; + uvec2 _size; + BufferPtr _vertexBuffer; + ProgramPtr _program; + VertexArrayPtr _vertexArray; + + //vk::DescriptorPool descriptorPool; + //vk::DescriptorSetLayout descriptorSetLayout; + //vk::DescriptorSet descriptorSet; + //vk::PipelineLayout pipelineLayout; + //vk::Pipeline pipeline; + + // Pointer to mapped vertex buffer + glm::vec4* _mapped { nullptr }; + stb_fontchar stbFontData[STB_NUM_CHARS]; + uint32_t numLetters; + + const char* const VERTEX_SHADER = R"SHADER( +#version 450 core +layout (location = 0) in vec2 inPos; +layout (location = 1) in vec2 inUV; + +layout (location = 0) out vec2 outUV; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main(void) +{ + vec2 pos = inPos; + pos.y *= -1.0; + gl_Position = vec4(pos, 0.0, 1.0); + outUV = inUV; +} +)SHADER"; + + const char* const FRAGMENT_SHADER = R"SHADER( +#version 450 core + +layout (location = 0) in vec2 inUV; + +layout (binding = 0) uniform sampler2D samplerFont; + +layout (location = 0) out vec4 outFragColor; + +void main(void) +{ + float color = texture(samplerFont, inUV).r; + outFragColor = vec4(vec3(color), 1.0); +} +)SHADER"; + +public: + enum TextAlign { alignLeft, alignCenter, alignRight }; + + bool visible = true; + bool invalidated = false; + + + TextOverlay(const glm::uvec2& size) : _size(size) { + prepare(); + } + + ~TextOverlay() { + // Free up all Vulkan resources requested by the text overlay + _program.reset(); + _texture.reset(); + _vertexBuffer.reset(); + _vertexArray.reset(); + } + + void resize(const uvec2& size) { + _size = size; + } + + // Prepare all vulkan resources required to render the font + // The text overlay uses separate resources for descriptors (pool, sets, layouts), pipelines and command buffers + void prepare() { + static unsigned char font24pixels[STB_FONT_HEIGHT][STB_FONT_WIDTH]; + STB_FONT_NAME(stbFontData, font24pixels, STB_FONT_HEIGHT); + + // Vertex buffer + { + GLuint buffer; + GLuint bufferSize = MAX_CHAR_COUNT * sizeof(glm::vec4); + glCreateBuffers(1, &buffer); + glNamedBufferStorage(buffer, bufferSize, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); + using BufferName = oglplus::ObjectName; + BufferName name = BufferName(buffer); + _vertexBuffer = std::make_shared(oglplus::Buffer::FromRawName(name)); + _vertexArray = std::make_shared(); + _vertexArray->Bind(); + glBindBuffer(GL_ARRAY_BUFFER, buffer); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), 0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (void*)sizeof(glm::vec2)); + glBindVertexArray(0); + } + + // Font texture + { + GLuint texture; + glCreateTextures(GL_TEXTURE_2D, 1, &texture); + glTextureStorage2D(texture, 1, GL_R8, STB_FONT_WIDTH, STB_FONT_HEIGHT); + glTextureSubImage2D(texture, 0, 0, 0, STB_FONT_WIDTH, STB_FONT_HEIGHT, GL_RED, GL_UNSIGNED_BYTE, &font24pixels[0][0]); + glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + using TextureName = oglplus::ObjectName; + TextureName name = TextureName(texture); + _texture = std::make_shared(oglplus::Texture::FromRawName(name)); + } + + compileProgram(_program, VERTEX_SHADER, FRAGMENT_SHADER); + } + + + // Map buffer + void beginTextUpdate() { + using namespace oglplus; + _mapped = (glm::vec4*)glMapNamedBuffer(GetName(*_vertexBuffer), GL_WRITE_ONLY); + numLetters = 0; + } + + // Add text to the current buffer + // todo : drop shadow? color attribute? + void addText(std::string text, vec2 pos, TextAlign align) { + assert(_mapped != nullptr); + const vec2 fbSize = _size; + const vec2 charSize = vec2(1.5f) / fbSize; + pos = (pos / fbSize * 2.0f) - 1.0f; + + // Calculate text width + float textWidth = 0; + for (auto letter : text) { + stb_fontchar *charData = &stbFontData[(uint32_t)letter - STB_FIRST_CHAR]; + textWidth += charData->advance * charSize.x; + } + + switch (align) { + case alignRight: + pos.x -= textWidth; + break; + case alignCenter: + pos.x -= textWidth / 2.0f; + break; + case alignLeft: + break; + } + + // Generate a uv mapped quad per char in the new text + for (auto letter : text) { + stb_fontchar *charData = &stbFontData[(uint32_t)letter - STB_FIRST_CHAR]; + _mapped->x = pos.x + (float)charData->x0 * charSize.x; + _mapped->y = pos.y + (float)charData->y0 * charSize.y; + _mapped->z = charData->s0; + _mapped->w = charData->t0; + ++_mapped; + + _mapped->x = pos.x + (float)charData->x1 * charSize.x; + _mapped->y = pos.y + (float)charData->y0 * charSize.y; + _mapped->z = charData->s1; + _mapped->w = charData->t0; + ++_mapped; + + _mapped->x = pos.x + (float)charData->x0 * charSize.x; + _mapped->y = pos.y + (float)charData->y1 * charSize.y; + _mapped->z = charData->s0; + _mapped->w = charData->t1; + _mapped++; + + _mapped->x = pos.x + (float)charData->x1 * charSize.x; + _mapped->y = pos.y + (float)charData->y1 * charSize.y; + _mapped->z = charData->s1; + _mapped->w = charData->t1; + _mapped++; + pos.x += charData->advance * charSize.x; + numLetters++; + } + } + + // Unmap buffer and update command buffers + void endTextUpdate() { + glUnmapNamedBuffer(GetName(*_vertexBuffer)); + _mapped = nullptr; + } + + // Needs to be called by the application + void render() { + _texture->Bind(oglplus::TextureTarget::_2D); + _program->Use(); + _vertexArray->Bind(); + for (uint32_t j = 0; j < numLetters; j++) { + glDrawArrays(GL_TRIANGLE_STRIP, j * 4, 4); + } + } +}; diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp new file mode 100644 index 0000000000..f3e3271a5d --- /dev/null +++ b/tests/render-perf/src/main.cpp @@ -0,0 +1,699 @@ +// +// Created by Bradley Austin Davis on 2016/07/01 +// 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 + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "Camera.hpp" +#include "TextOverlay.hpp" + +static const QString LAST_SCENE_KEY = "lastSceneFile"; +static const QString LAST_LOCATION_KEY = "lastLocation"; + +class ParentFinder : public SpatialParentFinder { +public: + EntityTreePointer _tree; + ParentFinder(EntityTreePointer tree) : _tree(tree) {} + + SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const override { + SpatiallyNestableWeakPointer parent; + + if (parentID.isNull()) { + success = true; + return parent; + } + + // search entities + if (entityTree) { + parent = entityTree->findByID(parentID); + } else { + parent = _tree ? _tree->findEntityByEntityItemID(parentID) : nullptr; + } + + if (!parent.expired()) { + success = true; + return parent; + } + + success = false; + return parent; + } +}; + + +class QWindowCamera : public Camera { + Key forKey(int key) { + switch (key) { + case Qt::Key_W: return FORWARD; + case Qt::Key_S: return BACK; + case Qt::Key_A: return LEFT; + case Qt::Key_D: return RIGHT; + case Qt::Key_E: return UP; + case Qt::Key_C: return DOWN; + default: break; + } + return INVALID; + } + + vec2 _lastMouse; +public: + void onKeyPress(QKeyEvent* event) { + Key k = forKey(event->key()); + if (k == INVALID) { + return; + } + keys.set(k); + } + + void onKeyRelease(QKeyEvent* event) { + Key k = forKey(event->key()); + if (k == INVALID) { + return; + } + keys.reset(k); + } + + void onMouseMove(QMouseEvent* event) { + vec2 mouse = toGlm(event->localPos()); + vec2 delta = mouse - _lastMouse; + auto buttons = event->buttons(); + if (buttons & Qt::RightButton) { + dolly(delta.y * 0.01f); + } else if (buttons & Qt::LeftButton) { + rotate(delta.x * -0.01f); + } else if (buttons & Qt::MiddleButton) { + delta.y *= -1.0f; + translate(delta * -0.01f); + } + + _lastMouse = mouse; + } +}; + + + +// Create a simple OpenGL window that renders text in various ways +class QTestWindow : public QWindow, public AbstractViewStateInterface { + Q_OBJECT + +protected: + void copyCurrentViewFrustum(ViewFrustum& viewOut) const override { + viewOut = _viewFrustum; + } + + void copyShadowViewFrustum(ViewFrustum& viewOut) const override { + viewOut = _shadowViewFrustum; + } + + QThread* getMainThread() override { + return QThread::currentThread(); + } + + PickRay computePickRay(float x, float y) const override { + return PickRay(); + } + + glm::vec3 getAvatarPosition() const override { + return vec3(); + } + + void postLambdaEvent(std::function f) override {} + qreal getDevicePixelRatio() override { + return 1.0f; + } + + render::ScenePointer getMain3DScene() override { + return _main3DScene; + } + render::EnginePointer getRenderEngine() override { + return _renderEngine; + } + + std::map> _postUpdateLambdas; + void pushPostUpdateLambda(void* key, std::function func) override { + _postUpdateLambdas[key] = func; + } + +public: + //"/-17.2049,-8.08629,-19.4153/0,0.881994,0,-0.47126" + static void setup() { + DependencyManager::registerInheritance(); + DependencyManager::registerInheritance(); + DependencyManager::set(); + DependencyManager::set(NodeType::Agent, 0); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + } + + QTestWindow() { + QThread::currentThread()->setPriority(QThread::HighestPriority); + AbstractViewStateInterface::setInstance(this); + _octree = DependencyManager::set(false, this, nullptr); + _octree->init(); + DependencyManager::set(_octree->getTree()); + getEntities()->setViewFrustum(_viewFrustum); + auto nodeList = DependencyManager::get(); + NodePermissions permissions; + permissions.setAll(true); + nodeList->setPermissions(permissions); + + ResourceManager::init(); + setSurfaceType(QSurface::OpenGLSurface); + auto format = getDefaultOpenGLSurfaceFormat(); + format.setOption(QSurfaceFormat::DebugContext); + setFormat(format); + + _context.setFormat(format); + _context.create(); + resize(QSize(800, 600)); + show(); + makeCurrent(); + glewExperimental = true; + glewInit(); + glGetError(); + setupDebugLogger(this); +#ifdef Q_OS_WIN + wglSwapIntervalEXT(0); +#endif + { + makeCurrent(); + _quadProgram = loadDefaultShader(); + _plane = loadPlane(_quadProgram); + _textOverlay = new TextOverlay(glm::uvec2(800, 600)); + glViewport(0, 0, 800, 600); + } + + _camera.movementSpeed = 50.0f; + + + // GPU library init + { + _offscreenContext = new OffscreenGLCanvas(); + _offscreenContext->create(_context.getContext()); + _offscreenContext->makeCurrent(); + gpu::Context::init(); + _gpuContext = std::make_shared(); + } + + // Render engine library init + { + _offscreenContext->makeCurrent(); + DependencyManager::get()->init(); + _renderEngine->addJob("RenderShadowTask", _cullFunctor); + _renderEngine->addJob("RenderDeferredTask", _cullFunctor); + _renderEngine->load(); + _renderEngine->registerScene(_main3DScene); + } + + reloadScene(); + restorePosition(); + + _elapsed.start(); + QTimer* timer = new QTimer(this); + timer->setInterval(0); + connect(timer, &QTimer::timeout, this, [this] { + draw(); + }); + timer->start(); + _ready = true; + } + + virtual ~QTestWindow() { + ResourceManager::cleanup(); + try { _quadProgram.reset(); } catch (std::runtime_error&) {} + try { _plane.reset(); } catch (std::runtime_error&) {} + } + +protected: + void keyPressEvent(QKeyEvent* event) override { + switch (event->key()) { + case Qt::Key_F1: + importScene(); + return; + + case Qt::Key_F2: + reloadScene(); + return; + + case Qt::Key_F5: + goTo(); + return; + + case Qt::Key_F6: + savePosition(); + return; + + case Qt::Key_F7: + restorePosition(); + return; + + case Qt::Key_F8: + resetPosition(); + return; + + case Qt::Key_F9: + toggleCulling(); + return; + + + default: + break; + } + _camera.onKeyPress(event); + } + + void keyReleaseEvent(QKeyEvent* event) override { + _camera.onKeyRelease(event); + } + + void mouseMoveEvent(QMouseEvent* event) override { + _camera.onMouseMove(event); + } + + void resizeEvent(QResizeEvent* ev) override { + resizeWindow(ev->size()); + } + +private: + + static bool cull(const RenderArgs* args, const AABox& bounds) { + float renderAccuracy = calculateRenderAccuracy(args->getViewFrustum().getPosition(), bounds, args->_sizeScale, args->_boundaryLevelAdjust); + return (renderAccuracy > 0.0f); + } + + void draw() { + if (!_ready) { + return; + } + if (!isVisible()) { + return; + } + update(); + + _offscreenContext->makeCurrent(); + + RenderArgs renderArgs(_gpuContext, _octree.data(), DEFAULT_OCTREE_SIZE_SCALE, + 0, RenderArgs::DEFAULT_RENDER_MODE, + RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); + + auto framebufferCache = DependencyManager::get(); + QSize windowSize = size(); + framebufferCache->setFrameBufferSize(windowSize); + // Viewport is assigned to the size of the framebuffer + renderArgs._viewport = ivec4(0, 0, windowSize.width(), windowSize.height()); + + renderArgs.setViewFrustum(_viewFrustum); + + // Final framebuffer that will be handled to the display-plugin + { + auto finalFramebuffer = framebufferCache->getFramebuffer(); + renderArgs._blitFramebuffer = finalFramebuffer; + } + + render(&renderArgs); + GLuint glTex; + { + auto gpuTex = renderArgs._blitFramebuffer->getRenderBuffer(0); + glTex = gpu::Backend::getGPUObject(*gpuTex)->_id; + } + + makeCurrent(); + { + glBindTexture(GL_TEXTURE_2D, glTex); + _quadProgram->Use(); + _plane->Use(); + _plane->Draw(); + glBindVertexArray(0); + } + + { + _textOverlay->render(); + } + + _context.swapBuffers(this); + + _offscreenContext->makeCurrent(); + framebufferCache->releaseFramebuffer(renderArgs._blitFramebuffer); + renderArgs._blitFramebuffer.reset(); + gpu::doInBatch(renderArgs._context, [&](gpu::Batch& batch) { + batch.resetStages(); + }); + _fpsCounter.increment(); + static size_t _frameCount { 0 }; + ++_frameCount; + if (_elapsed.elapsed() >= 500) { + _fps = _fpsCounter.rate(); + updateText(); + _frameCount = 0; + _elapsed.restart(); + } + } + +private: + class EntityUpdateOperator : public RecurseOctreeOperator { + public: + EntityUpdateOperator(const qint64& now) : now(now) {} + bool preRecursion(OctreeElementPointer element) override { return true; } + bool postRecursion(OctreeElementPointer element) override { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { + if (!entityItem->isParentIDValid()) { + return; // we weren't able to resolve a parent from _parentID, so don't save this entity. + } + entityItem->update(now); + }); + return true; + } + + const qint64& now; + }; + + void updateText() { + //qDebug() << "FPS " << fps.rate(); + { + _textBlocks.erase(TextBlock::Info); + auto& infoTextBlock = _textBlocks[TextBlock::Info]; + infoTextBlock.push_back({ vec2(98, 10), "FPS: ", TextOverlay::alignRight }); + infoTextBlock.push_back({ vec2(100, 10), std::to_string((uint32_t)_fps), TextOverlay::alignLeft }); + infoTextBlock.push_back({ vec2(98, 30), "Culling: ", TextOverlay::alignRight }); + infoTextBlock.push_back({ vec2(100, 30), _cullingEnabled ? "Enabled" : "Disabled", TextOverlay::alignLeft }); + } + + _textOverlay->beginTextUpdate(); + for (const auto& e : _textBlocks) { + for (const auto& b : e.second) { + _textOverlay->addText(b.text, b.position, b.alignment); + } + } + _textOverlay->endTextUpdate(); + } + + void update() { + auto now = usecTimestampNow(); + static auto last = now; + + float delta = now - last; + // Update the camera + _camera.update(delta / USECS_PER_SECOND); + { + _viewFrustum = ViewFrustum(); + _viewFrustum.setProjection(_camera.matrices.perspective); + auto view = glm::inverse(_camera.matrices.view); + _viewFrustum.setPosition(glm::vec3(view[3])); + _viewFrustum.setOrientation(glm::quat_cast(view)); + // Failing to do the calculation of the bound planes causes everything to be considered inside the frustum + if (_cullingEnabled) { + _viewFrustum.calculate(); + } + } + + getEntities()->setViewFrustum(_viewFrustum); + EntityUpdateOperator updateOperator(now); + //getEntities()->getTree()->recurseTreeWithOperator(&updateOperator); + { + PROFILE_RANGE_EX("PreRenderLambdas", 0xffff0000, (uint64_t)0); + for (auto& iter : _postUpdateLambdas) { + iter.second(); + } + _postUpdateLambdas.clear(); + } + + last = now; + } + + void render(RenderArgs* renderArgs) { + PROFILE_RANGE(__FUNCTION__); + PerformanceTimer perfTimer("draw"); + // The pending changes collecting the changes here + render::PendingChanges pendingChanges; + // Setup the current Zone Entity lighting + DependencyManager::get()->setGlobalLight(_sunSkyStage.getSunLight()); + { + PerformanceTimer perfTimer("SceneProcessPendingChanges"); + _main3DScene->enqueuePendingChanges(pendingChanges); + _main3DScene->processPendingChangesQueue(); + } + + // For now every frame pass the renderContext + { + PerformanceTimer perfTimer("EngineRun"); + _renderEngine->getRenderContext()->args = renderArgs; + // Before the deferred pass, let's try to use the render engine + _renderEngine->run(); + } + } + + bool makeCurrent() { + bool currentResult = _context.makeCurrent(this); + Q_ASSERT(currentResult); + return currentResult; + } + + void resizeWindow(const QSize& size) { + _size = size; + _camera.setAspectRatio((float)_size.width() / (float)_size.height()); + if (!_ready) { + return; + } + _textOverlay->resize(toGlm(_size)); + makeCurrent(); + glViewport(0, 0, size.width(), size.height()); + } + + void parsePath(const QString& viewpointString) { + static const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; + static const QString SPACED_COMMA_REGEX_STRING = "\\s*,\\s*"; + static const QString POSITION_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + "\\s*(?:$|\\/)"; + static const QString QUAT_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + + FLOAT_REGEX_STRING + "\\s*$"; + + static QRegExp orientationRegex(QUAT_REGEX_STRING); + static QRegExp positionRegex(POSITION_REGEX_STRING); + + if (positionRegex.indexIn(viewpointString) != -1) { + // we have at least a position, so emit our signal to say we need to change position + glm::vec3 newPosition(positionRegex.cap(1).toFloat(), + positionRegex.cap(2).toFloat(), + positionRegex.cap(3).toFloat()); + _camera.setPosition(newPosition); + + if (!glm::any(glm::isnan(newPosition))) { + // we may also have an orientation + if (viewpointString[positionRegex.matchedLength() - 1] == QChar('/') + && orientationRegex.indexIn(viewpointString, positionRegex.matchedLength() - 1) != -1) { + + glm::vec4 v = glm::vec4( + orientationRegex.cap(1).toFloat(), + orientationRegex.cap(2).toFloat(), + orientationRegex.cap(3).toFloat(), + orientationRegex.cap(4).toFloat()); + if (!glm::any(glm::isnan(v))) { + _camera.setRotation(glm::normalize(glm::quat(v.w, v.x, v.y, v.z))); + } + } + } + } + } + + void importScene(const QString& fileName) { + auto assetClient = DependencyManager::get(); + QFileInfo fileInfo(fileName); + //assetClient->loadLocalMappings(fileInfo.absolutePath() + "/" + fileInfo.baseName() + ".atp"); + _settings.setValue(LAST_SCENE_KEY, fileName); + _octree->clear(); + _octree->getTree()->readFromURL(fileName); + } + + void importScene() { + QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), "/home", tr("Hifi Exports (*.json *.svo)")); + if (fileName.isNull()) { + return; + } + importScene(fileName); + } + + void goTo() { + QString destination = QInputDialog::getText(nullptr, tr("Go To Location"), "Enter path"); + if (destination.isNull()) { + return; + } + parsePath(destination); + } + + void reloadScene() { + QVariant lastScene = _settings.value(LAST_SCENE_KEY); + if (lastScene.isValid()) { + importScene(lastScene.toString()); + } + } + + void savePosition() { + // /-17.2049,-8.08629,-19.4153/0,-0.48551,0,0.874231 + glm::quat q = _camera.getOrientation(); + glm::vec3 v = _camera.position; + QString viewpoint = QString("/%1,%2,%3/%4,%5,%6,%7"). + arg(v.x).arg(v.y).arg(v.z). + arg(q.x).arg(q.y).arg(q.z).arg(q.w); + _settings.setValue(LAST_LOCATION_KEY, viewpoint); + } + + void restorePosition() { + // /-17.2049,-8.08629,-19.4153/0,-0.48551,0,0.874231 + QVariant viewpoint = _settings.value(LAST_LOCATION_KEY); + if (viewpoint.isValid()) { + parsePath(viewpoint.toString()); + } + } + + void resetPosition() { + _camera.yaw = 0; + _camera.setPosition(vec3()); + } + + void toggleCulling() { + _cullingEnabled = !_cullingEnabled; + } + + QSharedPointer getEntities() { + return _octree; + } + +private: + render::CullFunctor _cullFunctor { [&](const RenderArgs* args, const AABox& bounds)->bool{ + if (_cullingEnabled) { + return cull(args, bounds); + } else { + return true; + } + } }; + + struct TextElement { + const glm::vec2 position; + const std::string text; + TextOverlay::TextAlign alignment; + }; + + enum TextBlock { + Help, + Info, + }; + + std::map> _textBlocks; + + gpu::ContextPointer _gpuContext; // initialized during window creation + render::EnginePointer _renderEngine { new render::Engine() }; + render::ScenePointer _main3DScene { new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) }; + OffscreenGLCanvas* _offscreenContext { nullptr }; + QOpenGLContextWrapper _context; + QSize _size; + RateCounter<200> _fpsCounter; + QSettings _settings; + + ProgramPtr _quadProgram; + ShapeWrapperPtr _plane; + + QWindowCamera _camera; + ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. + ViewFrustum _shadowViewFrustum; // current state of view frustum, perspective, orientation, etc. + model::SunSkyStage _sunSkyStage; + model::LightPointer _globalLight { std::make_shared() }; + QElapsedTimer _elapsed; + bool _ready { false }; + float _fps { 0 }; + TextOverlay* _textOverlay; + bool _cullingEnabled { true }; + QSharedPointer _octree; +}; + +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { + if (!message.isEmpty()) { +#ifdef Q_OS_WIN + OutputDebugStringA(message.toLocal8Bit().constData()); + OutputDebugStringA("\n"); +#endif + std::cout << message.toLocal8Bit().constData() << std::endl; + } +} + +const char * LOG_FILTER_RULES = R"V0G0N( +hifi.gpu=true +)V0G0N"; + +int main(int argc, char** argv) { + QApplication app(argc, argv); + QCoreApplication::setApplicationName("RenderPerf"); + QCoreApplication::setOrganizationName("High Fidelity"); + QCoreApplication::setOrganizationDomain("highfidelity.com"); + + qInstallMessageHandler(messageHandler); + QLoggingCategory::setFilterRules(LOG_FILTER_RULES); + QTestWindow::setup(); + QTestWindow window; + app.exec(); + return 0; +} + +#include "main.moc" diff --git a/tests/render-perf/src/stb_font_consolas_24_latin1.inl b/tests/render-perf/src/stb_font_consolas_24_latin1.inl new file mode 100644 index 0000000000..12eedd2f43 --- /dev/null +++ b/tests/render-perf/src/stb_font_consolas_24_latin1.inl @@ -0,0 +1,734 @@ +// Font generated by stb_font_inl_generator.c (4/1 bpp) +// +// Following instructions show how to use the only included font, whatever it is, in +// a generic way so you can replace it with any other font by changing the include. +// To use multiple fonts, replace STB_SOMEFONT_* below with STB_FONT_consolas_24_latin1_*, +// and separately install each font. Note that the CREATE function call has a +// totally different name; it's just 'stb_font_consolas_24_latin1'. +// +/* // Example usage: + +static stb_fontchar fontdata[STB_SOMEFONT_NUM_CHARS]; + +static void init(void) +{ + // optionally replace both STB_SOMEFONT_BITMAP_HEIGHT with STB_SOMEFONT_BITMAP_HEIGHT_POW2 + static unsigned char fontpixels[STB_SOMEFONT_BITMAP_HEIGHT][STB_SOMEFONT_BITMAP_WIDTH]; + STB_SOMEFONT_CREATE(fontdata, fontpixels, STB_SOMEFONT_BITMAP_HEIGHT); + ... create texture ... + // for best results rendering 1:1 pixels texels, use nearest-neighbor sampling + // if allowed to scale up, use bilerp +} + +// This function positions characters on integer coordinates, and assumes 1:1 texels to pixels +// Appropriate if nearest-neighbor sampling is used +static void draw_string_integer(int x, int y, char *str) // draw with top-left point x,y +{ + ... use texture ... + ... turn on alpha blending and gamma-correct alpha blending ... + glBegin(GL_QUADS); + while (*str) { + int char_codepoint = *str++; + stb_fontchar *cd = &fontdata[char_codepoint - STB_SOMEFONT_FIRST_CHAR]; + glTexCoord2f(cd->s0, cd->t0); glVertex2i(x + cd->x0, y + cd->y0); + glTexCoord2f(cd->s1, cd->t0); glVertex2i(x + cd->x1, y + cd->y0); + glTexCoord2f(cd->s1, cd->t1); glVertex2i(x + cd->x1, y + cd->y1); + glTexCoord2f(cd->s0, cd->t1); glVertex2i(x + cd->x0, y + cd->y1); + // if bilerping, in D3D9 you'll need a half-pixel offset here for 1:1 to behave correct + x += cd->advance_int; + } + glEnd(); +} + +// This function positions characters on float coordinates, and doesn't require 1:1 texels to pixels +// Appropriate if bilinear filtering is used +static void draw_string_float(float x, float y, char *str) // draw with top-left point x,y +{ + ... use texture ... + ... turn on alpha blending and gamma-correct alpha blending ... + glBegin(GL_QUADS); + while (*str) { + int char_codepoint = *str++; + stb_fontchar *cd = &fontdata[char_codepoint - STB_SOMEFONT_FIRST_CHAR]; + glTexCoord2f(cd->s0f, cd->t0f); glVertex2f(x + cd->x0f, y + cd->y0f); + glTexCoord2f(cd->s1f, cd->t0f); glVertex2f(x + cd->x1f, y + cd->y0f); + glTexCoord2f(cd->s1f, cd->t1f); glVertex2f(x + cd->x1f, y + cd->y1f); + glTexCoord2f(cd->s0f, cd->t1f); glVertex2f(x + cd->x0f, y + cd->y1f); + // if bilerping, in D3D9 you'll need a half-pixel offset here for 1:1 to behave correct + x += cd->advance; + } + glEnd(); +} +*/ + +#pragma once + +#ifndef STB_FONTCHAR__TYPEDEF +#define STB_FONTCHAR__TYPEDEF +typedef struct +{ + // coordinates if using integer positioning + float s0,t0,s1,t1; + signed short x0,y0,x1,y1; + int advance_int; + // coordinates if using floating positioning + float s0f,t0f,s1f,t1f; + float x0f,y0f,x1f,y1f; + float advance; +} stb_fontchar; +#endif + +#define STB_FONT_consolas_24_latin1_BITMAP_WIDTH 256 +#define STB_FONT_consolas_24_latin1_BITMAP_HEIGHT 170 +#define STB_FONT_consolas_24_latin1_BITMAP_HEIGHT_POW2 256 + +#define STB_FONT_consolas_24_latin1_FIRST_CHAR 32 +#define STB_FONT_consolas_24_latin1_NUM_CHARS 224 + +#define STB_FONT_consolas_24_latin1_LINE_SPACING 16 + +static unsigned int stb__consolas_24_latin1_pixels[]={ + 0x08262131,0xff904400,0x3ffe1fff,0x3b2206ff,0x2007913f,0x0000defa, + 0x64c00f32,0x0de5c402,0x00a614c0,0x4002ae62,0x98014c19,0x01aa881c, + 0x00d54400,0xb880154c,0x8330020b,0x1e980029,0xaa7d5dd4,0x2001d94f, + 0x3332a6e8,0x999ff0ff,0x37ffa207,0x2600df12,0x8000fffd,0x3fa005fd, + 0xfdff700f,0x8ffc409f,0x3ea02ff8,0x200dffff,0x0bfe0ff8,0x7d407ee0, + 0xfd10001f,0x9fff5007,0xcffff880,0x1ff104eb,0x320017fc,0x7d77e40f, + 0x17ee9f54,0xfd027ec0,0x7dc01fe1,0x0037c40d,0xb0017f22,0xffe8007f, + 0x7dc3fd80,0x741ff104,0x59ff701f,0x7401dfd7,0x2003f60e,0x3fa200fc, + 0x0ff44001,0x7dc6fdc0,0x32236604,0x3ba00eff,0x31003f60,0xdf90dd57, + 0xd93ea9f5,0x037e403f,0x803fc3fa,0x06f882fd,0x2006f980,0xa8800098, + 0xf903fb81,0x88040401,0x1ff441ff,0x0fb00000,0x00000000,0x00020000, + 0xffffa800,0xfadfb86f,0x7fc49f54,0x803ff301,0x200ff0fe,0x06f880fe, + 0x0000ff00,0x05f88000,0x40000fe6,0x0ff504fc,0x81540153,0x100affb9, + 0x22001573,0x31000ab9,0x26200157,0x731000ab,0xcffb8015,0x7dc4ffda, + 0x89f54fad,0x3fe80ff9,0x0ff0fe80,0x3e203fa0,0x807ddb36,0x01dd107f, + 0xdddb076c,0x1fb8bddd,0x1dd12fc0,0x3ff076c0,0x3f60ffc0,0xe98ff102, + 0xa84fffff,0x00dfffff,0x37ffffea,0x3fffea00,0x3fea00df,0x2a00dfff, + 0x80dfffff,0x3bb61ff8,0x3eb3ea3f,0x7f909f54,0xfd005fa8,0x7f401fe1, + 0xffaef880,0x1fe05ffe,0xdf504fd8,0xfffffff8,0x9db33746,0x09fb1b6b, + 0x80ff1bea,0x817ec2fd,0x33fe67f8,0x7dc3acfa,0x0efebacf,0xebacffb8, + 0xcffb80ef,0xb80efeba,0xefebacff,0xbacffb80,0x27e40efe,0x20df59f1, + 0x509f54fa,0x003fd8bf,0x401fe1fd,0x9fff107e,0x7407fe61,0x20ff500f, + 0x6f9803fd,0x777ccfe2,0x7fa8db6f,0x37cc7fb0,0x5fb0ff60,0x3fe9fe20, + 0x3ff105f3,0x7c43fe88,0x21ff441f,0x7f441ff8,0x220ffc43,0x0ffc43fe, + 0x3ff0ffa2,0x267f97d4,0x9f54fadf,0x1ff8ff10,0x1fe1fd00,0x7c417e20, + 0x704fb84f,0x217f405f,0xf9800ff8,0x47f47ea6,0xfd0f95f8,0x260ff885, + 0x21fe406f,0x2ff102fd,0x207ea6f9,0x8ff504fc,0x8ff504fc,0x8ff504fc, + 0x8ff504fc,0x8ff504fc,0x3fd3e47f,0x553eafea,0x261ff04f,0x1fd000ff, + 0xefcc81fe,0x260ff101,0x9bfb105f,0x7d45fb81,0x64df3005,0x9f34f98f, + 0x517ee1f2,0x407f98bf,0x817ec2fd,0x5cbf57f8,0x201ff80f,0x807fe1ff, + 0x807fe1ff,0x807fe1ff,0x807fe1ff,0xf8df31ff,0x47e4bf65,0x203514fa, + 0x00df52fd,0x107f87f4,0x7c403fff,0x440df306,0x7fc41ffe,0x4c00bf60, + 0x97ddf66f,0xf10db2fa,0x2217ec1f,0x20ff407f,0x2ff102fd,0x7c1f64fb, + 0xff17ec07,0x1fe2fd80,0x03fc5fb0,0x407f8bf6,0x4cdf32fd,0x9d4ff22f, + 0x9f7004fa,0xfe8013ee,0x6cc40ff0,0x20df103f,0x3bf506f9,0x7c4ff601, + 0xd9be6007,0x47f23f96,0x227fb06d,0x203ff07f,0x17ec0ff8,0x4df57f88, + 0x406f986e,0x80df33fd,0x80df33fd,0x80df33fd,0x80df33fd,0x64ff13fd, + 0x2a0bf60f,0x29f9004f,0x3fa005fa,0x1be00ff0,0x5fa837c4,0xfa801f90, + 0x4c009f76,0x87edba6f,0x2a09f0fd,0x3209f76f,0xb0bf704f,0x4dfe205f, + 0xf30bf0ff,0xf33fc80d,0xf33fc80d,0xf33fc80d,0xf33fc80d,0x3e3fc80d, + 0x03fd1ba7,0x3f6009f5,0x7400ff13,0x3a00ff0f,0x906f880f,0x401fa07f, + 0x001fe9ff,0xf96e9be6,0x017cdfe3,0x203fd3ff,0x7fd43ff9,0xf102fd81, + 0x27d7fecf,0xfb01fe63,0xfb01fe65,0xfb01fe65,0xfb01fe65,0xfb01fe65, + 0x1fc4ff45,0x9f501ff1,0xfe8bfe00,0x3e1fd001,0x101fd007,0x40df50df, + 0xfbf9007e,0x26f9800d,0xfdb9f56d,0x7e401fb7,0x7fdc06fd,0xb02ffede, + 0x89fe205f,0x4feefffc,0x1fe80ff1,0x1fe80ff1,0x1fe80ff1,0x1fe80ff1, + 0x1fe80ff1,0xb87ef7f2,0x2a9f505f,0x647f984f,0x21fd002f,0x01fd007f, + 0xfb99dff1,0x803f403f,0x4003fff8,0x6c5f26f9,0x01ffe8ef,0x401fffc4, + 0x01dfffda,0x2fd40ff2,0x0e77ff4c,0x3fe203ff,0x7c407fe0,0x4407fe0f, + 0x407fe0ff,0x07fe0ff8,0xff107fc4,0x807fd4fd,0x509f54fa,0x004fa8bf, + 0x401fe1fd,0xfff880fe,0x7400deff,0x01ff6007,0x0fb9be60,0x7ec00302, + 0x027dc007,0x4fc827dc,0x3f203f70,0xfc8bf704,0xfc8bf704,0xfc8bf704, + 0xfc8bf704,0xf30bf704,0x07ffd9df,0x84faa7d4,0x07fc41fe,0x07f87f40, + 0xdf101fd0,0x07f80022,0x2000ff60,0x00fe65fa,0x001fec00,0x98103fe6, + 0x0ffcc1ff,0xff100fc8,0x440ffa87,0x07fd43ff,0xff50ffe2,0x543ff881, + 0x1ffc40ff,0x7dc03fea,0x5401dfff,0xfc89f54f,0xd00df705,0x6c01fe1f, + 0x006f883f,0xf5006f98,0x9fb0001f,0xa80006e8,0xfd8000ff,0xfc86fcdf, + 0x04ffecdf,0xdff701f6,0x2e07ffd9,0x3ffeceff,0xfd9dff70,0x3bfee07f, + 0xf703ffec,0x07ffd9df,0x5000cfec,0xfa93ea9f,0x013f600f,0x401fe1fd, + 0x37c41efb,0x013f6600,0x33007fea,0x2e07fdc4,0xf500505f,0x7e40003f, + 0xfd703fff,0x6e805dff,0xdfffea80,0x7fff5401,0x7ff5401d,0x7f5401df, + 0x75401dff,0x7c01dfff,0x54fa8005,0x00bfe29f,0x775c3fd1,0xddff0ffe, + 0x7fff409d,0x2600df12,0xf300effe,0xf7007fff,0x207fffdf,0x7fdcdffb, + 0x07ffff30,0x80022000,0x0017e001,0x00060003,0x0018000c,0x07220030, + 0x7d53ea00,0x26007fb4,0x3bbbae6f,0x9ddddb0e,0xd12ecb80,0x0f3a600b, + 0x0019bd30,0x073bbb22,0x17bdb730,0x00337a60,0x00000000,0x00000000, + 0x00000000,0x00000000,0x4d3ea9f5,0x00154004,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x20000000,0x009f54fa, + 0x77777400,0xeeeeeeee,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x4c000000,0x0007d33e,0x77777740,0x0eeeeeee, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x54400000,0x2a20001a,0x0154c01a,0x54400b20, + 0xaaa98000,0x99953100,0x032e0059,0x10053066,0x22004177,0x04c801aa, + 0x01aa8800,0x500d5440,0x51017997,0x51000035,0x0bb88035,0x00d54402, + 0x0007fea0,0xf500ffa2,0x3e6009ff,0x3fef9803,0xfffff700,0xffffb89f, + 0xf8804fff,0x3e0ff886,0x3ffe202f,0x5404ebcf,0x0fec01ff,0x07fd1000, + 0x103fe880,0x05fffffd,0x80007fea,0x7c403fe8,0x04ebcfff,0x88003ff5, + 0x3a2001fe,0x46fdc01f,0x7dc404fb,0x6baec00b,0xabcffd80,0x3fff24ec, + 0x804fb9df,0x41dd03fb,0x236600fd,0x800effc8,0x3e601fe8,0x3fd10005, + 0x01fe8800,0x008833f6,0x20007fa2,0xd9801fe8,0x00effc88,0x00007fa2, + 0x00000000,0x9fffffd5,0x036cf640,0x98407bee,0xf54fffff,0x001fd009, + 0x00020000,0x0007f400,0x44000000,0x0000007f,0x00200000,0x40153000, + 0xa802a62a,0x2a802a62,0x33fb7ff2,0x3bfa204d,0x007fe201,0x53fffff2, + 0x27d404fa,0x40015540,0x553002aa,0x50555555,0x01aa807f,0x554c1544, + 0xf82aaaaa,0x2aa8002f,0x802aa800,0x50aa02a9,0x55555555,0xf102fd81, + 0xf8817ecf,0x7c40bf67,0x1f61ff37,0x5c02aa80,0xfff9005f,0x013ea9ff, + 0xff8803fb,0x3fe2002f,0xfffc802f,0xdf07ffff,0x6406fb80,0xffffc85f, + 0xffb87fff,0x3ffe2003,0x3ffe2002,0x41ffe402,0x7fffc6f8,0x6c0fffff, + 0x6cff102f,0x6cff102f,0x2eff102f,0x4401ba5f,0x3f202fff,0xffff7003, + 0x8813ea9f,0xfffa805f,0x3ffea004,0xaacfc804,0x5f902aaa,0xf103ff80, + 0x559f901f,0xff985555,0xfa802eff,0x3ea004ff,0x7fe404ff,0x5546f886, + 0x0aaadfda,0x7f8817ec,0x3fc40bf6,0x5fe205fb,0x4017e7fa,0x3604fffa, + 0xfff3002f,0x813ea9ff,0xafd802fc,0x2bf6007f,0x03fc807f,0x2a02fc40, + 0x04fd80ff,0x3fa007f9,0x404ffd89,0x2007fafd,0x6407fafd,0x37c42fef, + 0xfd809f70,0x7ecff102,0x7ecff102,0x3e2ff102,0xb004faef,0x7f40ff5f, + 0x3fff2002,0x7c09f54f,0xfd7f8807,0x3aff1003,0x01fe401f,0x3a00fec0, + 0x807fcc4f,0x5f9803fc,0xf8827fd4,0xf1003fd7,0xfc807faf,0x037c46fb, + 0x2fd809f7,0x17ecff10,0x0bf67f88,0xffd33fc4,0x7f88019f,0x0bfa03fd, + 0x29ffd500,0x07f504fa,0x13ee9f50,0x27dd3ea0,0x2000ff20,0x7fcc04fa, + 0xfc80ff60,0x886f9803,0x74fa81ff,0x29f5009f,0x2bf204fb,0x81be22fd, + 0x17ec04fb,0x0bf67f88,0x05fb3fc4,0x3fae1fe2,0x53ea03ff,0x07fb04fb, + 0x4fa84c00,0xfb001fd0,0x3601fe65,0xc80ff32f,0x1fd0003f,0xdf34fd80, + 0xf003fc80,0xb07f905f,0x201fe65f,0x40ff32fd,0x226faafc,0x013ee06f, + 0x9fe205fb,0x4ff102fd,0x0ff102fd,0x417ff7ee,0x20ff32fd,0x500006fb, + 0x00bf309f,0x01fe8ff1,0x03fd1fe2,0x777777e4,0x01fdc04e,0x0bf63fe2, + 0xdddddf90,0x43ffa89d,0x23fc43fb,0x1fe201fe,0x4bf203fd,0x40df11fe, + 0x17ec04fb,0x0bf67f88,0x05fb3fc4,0x9be41fe2,0x23fc43ff,0x2ff881fe, + 0x84fa8000,0x5fa801fc,0xbf5027e4,0x3f204fc8,0x06ffffff,0x7e4037c4, + 0xffc806fe,0xa86fffff,0x0ff89eff,0x13f22fd4,0x27e45fa8,0x2bf52fc8, + 0x13ee06f8,0x3e205fb0,0x7c40bf67,0x7c40bf67,0x2fdcdb07,0x09f917ea, + 0x0620bff2,0x3e227d40,0x985fb006,0x30bf607f,0x01fe40ff,0x8803f900, + 0xfc801fff,0x7fec4003,0x42fd82ff,0x0bf607f9,0x8bf20ff3,0x206f89ff, + 0x17ec04fb,0x0bf67f88,0x05fb3fc4,0xb2f41fe2,0x4c2fd89f,0x3fff607f, + 0x5004fedd,0x802fb89f,0x99999ff8,0x9ff881ff,0x01ff9999,0x4c0007f9, + 0x05fb805f,0x20007f90,0xff886fe9,0x1ff99999,0x9999ff88,0x2fc81ff9, + 0x81be33ee,0x1fe404fb,0x0ff25fa8,0x07f92fd4,0x9f04d7ea,0x7c43ff71, + 0xff99999f,0x3fffaa01,0x7f9002ce,0xff5007e8,0x9fffffff,0xffffff50, + 0x3f209fff,0x07f40003,0x64013ee0,0x3a20003f,0x7fffd42f,0xa84fffff, + 0xffffffff,0x222fc84f,0x2e06f9ff,0x827dc04f,0x413ee4fc,0x413ee4fc, + 0xfdffb4fc,0xfa87ffff,0xffffffff,0x0077c404,0x9f517f40,0x3337f600, + 0xd86fdccc,0xdcccccdf,0x007f906f,0x5c04fa80,0x07f9004f,0x6c2fe800, + 0xdcccccdf,0xccdfd86f,0xc86fdccc,0x37e7e42f,0xf9809f70,0x30ffcc1f, + 0x1ff983ff,0xff307fe6,0x3fffb6a3,0xcdfd80ce,0x06fdcccc,0x37601fd4, + 0x6c6fd989,0x0ff8800f,0xff887fe0,0x3207fe00,0x7f80003f,0xc8027dc0, + 0x4c15003f,0x3fe20ffb,0xf887fe00,0x907fe00f,0x6fff885f,0x32013ee0, + 0x4ffecdff,0xfecdffc8,0xcdffc84f,0x3f704ffe,0xf007fc40,0x00fe403f, + 0xdfffffb1,0xa802fcc3,0x527ec05f,0x84fd80bf,0xeeeeeefc,0x80bee006, + 0xdf9004fb,0xf8dddddd,0x41ffffff,0x27ec05fa,0x4fd80bf5,0x7fe417e4, + 0x7ff776c6,0xfd700eee,0x75c05dff,0x2e02efff,0x202efffe,0x17ea00fc, + 0x14c09fb0,0x82cdba80,0x7fb001ca,0x3f66fa80,0x6437d403,0x7fffffff, + 0xb80f2200,0xfff9004f,0xcb8fffff,0x7fb02cdd,0x3f66fa80,0x3237d403, + 0x46ff882f,0xffffffff,0x8001800f,0x90018001,0x403fd80b,0x000006fa, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x0d544000,0x2600aa60,0x54c014c1,0x14c19802,0x400154c0, + 0xcb9802c9,0x0332e02c,0x30a60f22,0x00332a05,0x4000b260,0x0cca83cc, + 0x01e64000,0x2e200b26,0x6644020b,0x00cca801,0xe8803ca8,0x7ffd403f, + 0xf83fe204,0x3ffea02f,0xf83fe204,0x3ffea02f,0x3f7ee004,0x3ffff604, + 0x7f7ec0ef,0x220fe40f,0x05ff11ff,0xa8003bee,0x6c003fff,0x03bee05f, + 0x202fec00,0x2203fffa,0x4eacffff,0x9d95ff70,0x9003bee0,0x1fe880bf, + 0x7dc6fdc0,0xfd83ba04,0xf71bf700,0xfb077409,0x2e37ee01,0x2a7d004f, + 0x261bf907,0x54fea4fd,0x2213ea3f,0x807fa0ff,0xfa800ef9,0xb003fc8d, + 0x1df3007f,0x403fd800,0x03fc8dfa,0xdffb31d3,0xfffdb881,0x3be603ef, + 0x0017f200,0x00000000,0x00000000,0x7b8dd800,0x6f981ff0,0x8a7c47ee, + 0x020200ee,0x22002620,0x4c400cc1,0x00262000,0x18800988,0x011000cc, + 0x0ffb7fe2,0xf9004c40,0x5555550b,0xaa981555,0x982aaaaa,0x2aaaaaaa, + 0xaaaaaaa8,0x555540aa,0x200aaaaa,0x7cc002aa,0x86f882ff,0x44bee5fa, + 0x0005f94f,0x00000000,0x00000000,0x00000000,0x001ff982,0xff0bf900, + 0x1fffffff,0xffffffc8,0xffffc87f,0xfff87fff,0x40ffffff,0xffffffff, + 0x5fff100f,0x26013000,0x30ffd45f,0xf33fb3bf,0x9dfb7009,0xcefdb801, + 0x677edc00,0x677edc00,0xdfdb7100,0x3b6e203b,0xb7101def,0x2203bdfd, + 0x01defedb,0x01bffb2a,0x7039dfb7,0xfb5550bf,0xfc81555b,0x82aaaaac, + 0xaaaaacfc,0xdfdaaa82,0x55540aaa,0x00aaadfd,0x1009fff5,0x83bdfdb7, + 0x07bee5f9,0x7f4fffee,0x76f7ec00,0xbdfb02ff,0x3f605ffd,0xb02ffede, + 0x05ffdbdf,0xfffddff7,0xfddff705,0xdff705ff,0xf705fffd,0x05fffddf, + 0x5ffddffb,0xffdffd30,0x404fb87f,0x1fe404fb,0x0007f900,0xfb8013ee, + 0xff5fb004,0xfddff700,0x8afcc5ff,0x426200ff,0x27e402fc,0x9f903fea, + 0x7e40ffa8,0x3207fd44,0x207fd44f,0x43fdc419,0x43fdc419,0x43fdc419, + 0x23fdc419,0x1bea0dfc,0x3ff513fa,0x3ee027dc,0x001fe404,0x2e0007f9, + 0x13ee004f,0x0ff5fe20,0xff710660,0x07f8afcc,0xf1017e60,0xf88ff20d, + 0x7c47f906,0x7c47f906,0x4007f906,0x3fe000ff,0x003fe000,0x0df30ff8, + 0x05fa87fa,0x027dcbf9,0x7f9013ee,0x001fe400,0x2e004fb8,0x74fa804f, + 0x7fc0009f,0x27fcbf30,0x5400fe80,0x549f504f,0x549f504f,0x549f504f, + 0x009f504f,0xfb000fec,0x00fec003,0x0fee3fb0,0x05f927e4,0x04fb9be2, + 0x3f2027dc,0x00ff2003,0x70027dc0,0x25fb009f,0x360007f9,0x7ccbf31f, + 0x4bee00df,0x77dc1dec,0x5feeeeee,0x3bbbbbee,0x3bee5fee,0x5feeeeee, + 0x3bbbbbee,0xec985fee,0x981ffffe,0x1ffffeec,0xfffeec98,0xfeec981f, + 0x05fb1fff,0x01fe97ea,0x403fa9fe,0x77e404fb,0xc84eeeee,0x4eeeeeef, + 0x70027dc0,0x47f8809f,0x764c01fe,0xf31ffffe,0x80efe98b,0xffbfd5f8, + 0x77777e43,0x3f24eeee,0xeeeeeeee,0x3bbbbf24,0x3f24eeee,0xeeeeeeee, + 0x6677fdc4,0x7fdc1ffc,0x41ffccce,0xfccceffb,0x677fdc1f,0x3fb1ffcc, + 0x1fe97ea0,0x03fa9fe0,0x3f2027dc,0x86ffffff,0xfffffffc,0x0027dc06, + 0x5fa809f7,0xff7027e4,0x23ff999d,0x4fe885f9,0x33f98fe8,0x002fdc9f, + 0x7dc00bf7,0x017ee005,0xfd81ff88,0x3607fe21,0x207fe21f,0x07fe21fd, + 0x817e47f6,0x40bf64fb,0x007266f8,0x3fc809f7,0x000ff200,0xf70027dc, + 0x4c2fd809,0x03ff107f,0x417e63fb,0xb9fdc6f9,0xffa97e1f,0x01ff5000, + 0x4003fea0,0xf5000ffa,0xfa87fa0b,0x7d43fd05,0x7d43fd05,0x3ee3fd05, + 0x7e45fb05,0x000bf704,0x3fc809f7,0x000ff200,0xf70027dc,0x4cffc409, + 0xa81ff999,0x263fd05f,0x44df305f,0x7c4bea6f,0x80067f44,0xfd000cfe, + 0x33fa0019,0x446fa800,0x1bea1ffd,0x7d43ffb1,0x50ffec46,0x1ffd88df, + 0x7fa85ff1,0x3e60ffc4,0x700faa1f,0x03fc809f,0x4000ff20,0x3ee004fb, + 0x3fffea04,0xa84fffff,0x8ffec46f,0xfb9935f9,0xf10fec5f,0xf983fb7d, + 0x0eccbdff,0x65efffcc,0x7ffcc0ec,0x4c0eccbd,0xeccbdfff,0x373bfe20, + 0x3e21feff,0xfeffdcef,0x373bfe21,0x3e21feff,0xfeffdcef,0x3737fee1, + 0xdffb82ff,0x7fc3ffdc,0x2027dc07,0x3f2003fc,0x09f70003,0xfb027dc0, + 0xfb99999b,0xb9dff10d,0x3e63fdff,0x45dfff35,0xfffa83fa,0xffffb102, + 0xffb101df,0xb101dfff,0x01dfffff,0xdfffffb1,0xdfffe981,0x7f4c1fc8, + 0x41fc8dff,0xc8dfffe9,0x7fff4c1f,0x7541fc8d,0x2a01efff,0xc81efffe, + 0x027dc05f,0xfc800ff2,0x09f70003,0xf8827dc0,0x207fe00f,0xc8dfffe9, + 0x0002201f,0x02620008,0x20009880,0x26200098,0x40008800,0x00440008, + 0x06000220,0x40600600,0xeeffeeed,0x7777e40e,0xefc86eee,0xd86eeeee, + 0xeeeffeee,0x7ff776c0,0x0bf50eee,0x02204fd8,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0xffffff80,0x7fe40fff,0xc87fffff, + 0x7fffffff,0xfffffff8,0x7fffc0ff,0xfb0fffff,0x006fa807,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x40f32000,0xca814c29,0x055cc00c,0x203cc800,0x98014c29,0x0bb8802c, + 0x40044002,0x298003c8,0x0310014c,0x8000b260,0x157300cb,0x76c17a20, + 0x00f32000,0x32a0aa62,0x027d400c,0xf882fec0,0x205ff11f,0x3ee00efb, + 0x36001eff,0x47fe205f,0x7d402ff8,0x3fe203ff,0x204eacff,0x203ffffa, + 0x7c4006f8,0x005ff11f,0x3fea01f6,0x3fb0003f,0x4fffff98,0x1fd06f88, + 0x8817f600,0x706ffffb,0x7ff401df,0x80ff6000,0x07fa0ff8,0x5100ef98, + 0xb005ffd7,0x0ff8807f,0xdfa807fa,0x1d303fc8,0x201dffb3,0x2ffdcffa, + 0x8800df10,0x007fa0ff,0x37ea02fc,0xd8003fc8,0x26ffea1f,0x37c44feb, + 0xfd800fe8,0x37ffe603,0x3be602ac,0x400bf700,0x10100098,0x20013100, + 0x26200ffb,0x00202000,0x20019831,0x717ec008,0x01be20bf,0xb7004040, + 0x18807fff,0x7ec000cc,0xff10bfa1,0xfe837c41,0x1004c400,0x310007ff, + 0x00000001,0x20000000,0x000004fd,0x00000000,0x6f983fe0,0x0000df10, + 0xfeffe980,0x000001ff,0x17ea3fb0,0x0df127dc,0x200003fa,0x000003fc, + 0x05e88026,0x82f443d9,0x417a21ec,0x17ee01ec,0x00e77edc,0x80e77edc, + 0x03d905e8,0x8073bf6e,0x413ee2fe,0x7ddb36f8,0x3bfb6e20,0xf13fe81d, + 0x6dc03ffd,0x65401cef,0xf91ffdee,0x44dfd305,0x701fd06f,0x7c0bdddd, + 0x7775c007,0x407f305e,0x45fb06f8,0x45fb06f8,0x05fb06f8,0x3fa61fdc, + 0x303fffef,0x7fffdffd,0x3f60df10,0x7f7ff4c2,0x2df903ff,0xdf100ffa, + 0x0bffdff5,0xfffddff7,0x5f52fdc5,0xffd309f7,0xb107fffd,0x3fffdfff, + 0xfff707f6,0xfe837c4f,0x7ffffc80,0x2aa2bf30,0xffff9009,0x8813ea0f, + 0x445fb06f,0x445fb06f,0x205fb06f,0x27f40ff9,0x3fa07fea,0x220ffd44, + 0xe85fb06f,0x40ffd44f,0x01efeff8,0x4c33ffe2,0x220cc1ff,0xd8bf27fb, + 0x9fd0bf17,0x7e41ffa8,0xfe8ff42d,0xff3dfb10,0x0fe837c4,0xfa83fc40, + 0x2fffffee,0xfa83fc40,0x6c1be204,0x6c1be22f,0x6c1be22f,0x3fffe62f, + 0xfc82fd44,0xfc82fd45,0xfd837c45,0x7e417ea2,0x00bffb05,0x9f709ff1, + 0xfe87fc00,0x547f93e0,0x44bf905f,0x3e3fb07f,0xf0dff98f,0x303fe21f, + 0x7f8803ff,0x537bff70,0x7c403ff9,0x4409f507,0x445fb06f,0x445fb06f, + 0x4c5fb06f,0x05f902ef,0x05f91be2,0x0df11be2,0x02fc8bf6,0xefe88df1, + 0x88ff11ff,0x00bf307f,0x50fe8fec,0x3f23fc5f,0x7dcdf102,0x3fa3fb04, + 0x13fc3ffc,0x7ff445ff,0xb83fc401,0x40bf904f,0x09f507f8,0x2fd837c4, + 0x17ec1be2,0x8bf60df1,0x07fa04f9,0x00ff47f8,0xb06f88ff,0xf00ff45f, + 0xdfb4fd8f,0x6f88df31,0xd930df30,0x323ffffd,0x37c4fb2f,0x27f807fa, + 0x23fb03fc,0x7c41effd,0x337ffe27,0x202efbef,0x09f707f8,0x7f881be6, + 0x7c40bf50,0x7c45fb06,0x7c45fb06,0x7cc5fb06,0xf807fa04,0xff00ff47, + 0x5fb06f88,0x4ff00ff4,0x56ff47f8,0x9837c45f,0x677fdc6f,0x9f51ffcc, + 0x745fa97e,0xfd9fe01f,0x3f23fb02,0x225fa80d,0xb0dffeef,0x83fc409f, + 0x0ff105fa,0x5fb83fc4,0x7ec1be20,0x7ec1be22,0x7ec1be22,0xfd80b222, + 0xfd8df102,0xf88df102,0x7ec5fb06,0x7ccdf102,0x17fffc46,0x2fd41be2, + 0x3fb03ff1,0x44bf3fe2,0x817ec1fe,0x413f26f8,0x20df31fe,0x45be22fd, + 0x07f88000,0x17ea0ff1,0xbf707f88,0x7c40ff80,0x2207fc2f,0x207fc2ff, + 0x64002ff8,0xc8bf704f,0xf0bf704f,0x22ff881f,0x4bf704fc,0xfffa87f9, + 0xfc837c40,0x7f417ea3,0x373ffea1,0x04fc84ff,0x42fdcbf7,0x0ffa1ffb, + 0x06f88ff5,0xb03fc400,0x02fe889f,0x2fdc1fe2,0xfe88bfe0,0xd117fc2f, + 0x22ff85ff,0x3a62ffe8,0x307fe204,0x1ff883ff,0x7fc0ffcc,0x88bffa22, + 0x0ffcc1ff,0xffd50ffe,0xfa86f883,0x36237d46,0x7ffd41ff,0x3ff102ef, + 0x3e21ff98,0x87ffea1f,0xffdcdff9,0x00037c41,0xff981fe2,0x404fecbd, + 0x0bf707f8,0xefcdffb8,0x6ffdc2fd,0x5c2fdefc,0xfdefcdff,0xb803ff62, + 0x3ffdcdff,0xfb9bff70,0x37fee07f,0x5c2fdefc,0x3ffdcdff,0xffceffa8, + 0x3e20efef,0x1ffdccef,0x7ee77fc4,0x5fd41fef,0x9bff7001,0xffb07ffb, + 0x83f99fdb,0x81dfffd8,0xcc8006f8,0x1cccffcc,0x177ffec4,0xcffcccc8, + 0x00df71cc,0xf71bffd5,0x1bffd505,0xffd505f7,0x7dc5f71b,0xfffea806, + 0x7ff5401e,0x7f5401ef,0xa82fb8df,0x201efffe,0xd1dfffea,0xfffdb8bf, + 0xffd300de,0xd83f91bf,0xffea8007,0x3ff201ef,0x0c03f93e,0x20017a20, + 0xffffffff,0x3e00603f,0xffffffff,0x4400bd73,0x00044000,0x00020022, + 0x000c0006,0x00c00044,0x01000040,0x3c800440,0x2000c000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x55530000,0x53035555,0x54c00557, + 0xba9800aa,0x2aaaa60a,0x2a600aaa,0x0032e009,0x32205950,0x020bb883, + 0x3c881654,0x6440736e,0x09999953,0x80357510,0x209aca98,0x20aa00a9, + 0x2e014c29,0x0164c03f,0x1dd127dc,0x64c076c0,0xfffffc82,0x7ffe44ff, + 0x3ee03fff,0x904fffff,0x2bffffff,0xfffffffc,0xfffffb81,0x000fe80d, + 0x320bffd3,0x7fffc41f,0x3fa64eac,0x6c3f905f,0x1fc80ffd,0x40fffff9, + 0xefffffc8,0xfffffc81,0x440bf65f,0x88ffc47f,0x1ffe02ff,0x203fffa8, + 0x3f62fff8,0x3a0df504,0x567e40ff,0x5dc1aaaa,0x81ffdb9a,0xecabcffd, + 0x5e7ff444,0x55535dca,0xfd83fd55,0x0efc989c,0xea8007f4,0x84fa85fa, + 0xeffd98e9,0x217ebaa0,0x83fb04fa,0x266624fa,0x2fbf607f,0x360ffda9, + 0x3baabcef,0x3fc40bf6,0x1fe83fe2,0x7d413f60,0x3e03fc8d,0x41fea1ff, + 0x1ffd03fd,0x40001fc8,0x0f7dc4fe,0x8077ec08,0xf70ff400,0xfd17ea07, + 0x45f88001,0x11000ee8,0x3a22fc40,0x22ffe40e,0xff100ee8,0x3e03fe20, + 0x003ff32f,0x1fe205fb,0x20000404,0x9300cc18,0xf885fd05,0x9035100f, + 0xfb80003f,0x4007fe25,0x20000ffa,0x4cdf11fe,0x743f91bb,0x2fc4000f, + 0x80000bf2,0x417e45f8,0x3f23fda9,0x441fe202,0x2e5fb06f,0x05fb005f, + 0x80001fe2,0x00000098,0x5fa8bf70,0x003f9000,0x3ee2fc80,0x00ff6005, + 0x3ee3fd00,0x22ffff52,0x3603fa4f,0x265f884e,0x2a9d104f,0xbf101cfd, + 0xc99827cc,0x8809f34f,0x10bfa07f,0x007fd4ff,0x7f8817ec,0x02f7775c, + 0xeeb82fb8,0x440005ee,0x70bf60ff,0xf90bdddd,0xf3000335,0x001fe41d, + 0xe80003fd,0xdf11f91f,0x3fa5f823,0x44077ec0,0x4403fa5f,0xffeefcdf, + 0x3fa5f884,0x21dfff00,0x3fc400fe,0xf519ff50,0x177f447f,0x7c40bf60, + 0x3ffffe47,0xfc82fb80,0x80007fff,0x90ff13fd,0xf90fffff,0x205bffff, + 0xd85feee8,0x83fe002f,0x403cccc9,0x3eafb1fe,0x07f4fb03,0x90980bfb, + 0x7ffc405f,0x2603fea3,0x4cc05f90,0x2200bf20,0x7ffcc07f,0xffe981ff, + 0x02fd80be,0x3fc40ff1,0x2017e440,0xa80007f8,0x8809f76f,0xdccca87f, + 0xff982fff,0x3fa0cfff,0xa8ff1002,0x7406ffff,0x0beadd1f,0x361fd3ec, + 0x17e6005f,0xff07ff10,0x002fcc03,0x7c4017e6,0x7ffec407,0x3ffae03f, + 0x8817ec3f,0x81fe207f,0x403fffd9,0x2da807f8,0x07fa7fe0,0x6400ff10, + 0x6fd9807f,0x7c400bf6,0x37d4cc47,0x8bec7fa0,0x7f4dd04f,0x74005fd8, + 0x83fc400f,0x03fa02fd,0x2001fd00,0x3fa607f8,0x405ffc8c,0x3f65ffda, + 0x440ff102,0x273fa07f,0x03fc4009,0xfc80fffc,0x7f8806fd,0x007fe200, + 0x03fc97f4,0x7cc03fe0,0xfc8ff406,0x7c737fd0,0x01bf7fa5,0x33265f70, + 0xfd837c42,0xd915f702,0x997dc05b,0x0ff102cc,0x3fe617fc,0xb2ffa802, + 0x81fe205f,0x0bf307f8,0xe807f880,0xfff103ff,0x00ff1007,0xf9002fe8, + 0xe801bee7,0x80df303f,0x267f51fe,0x47f37ffd,0x002ffafe,0x27ff4bf1, + 0x17ec1be2,0xecfaafc4,0x3a5f881f,0x0ff104ff,0x2fe417e6,0x7f94fc80, + 0xf8817ea0,0x8009f707,0xff9807f8,0x401ff603,0x3e2007f8,0x23fd000f, + 0xf5002ff8,0x40df301f,0x11fa0ff9,0x1fd07ec1,0xfd005ff3,0x113eff21, + 0x20bf60df,0x17dc20fe,0xfbfc87f4,0x2e0ff104,0x00bf704f,0x827dcff2, + 0x1fe204fc,0x220037dc,0x03ff007f,0xf8801fec,0x09fb1007,0xff91bee0, + 0xefe83105,0x20bb7cc0,0x77d46fc8,0x7f43fc80,0x5c03ff50,0x9f39f33f, + 0x5fb06f88,0xfe883fb8,0xcf99fdc0,0x0ff104f9,0x3fa03fea,0x8ffcc013, + 0x7fcc1ff9,0x441fe201,0x3e2003ff,0x206fb807,0xf1000ffa,0xfb999b0f, + 0xb999b0bf,0xfd881fff,0x44feddff,0xecdeffe8,0xffbdfd6f,0x59df703f, + 0x1fd09fd7,0x7c40ffdc,0x3bfbbf66,0x7ec1be23,0x3e61be22,0xfb37c41e, + 0xf107dfdd,0x667ff40f,0x3bf66ffc,0x44ffedcd,0xffecdffc,0x703fc404, + 0x88013bff,0x3bfb207f,0x003ff500,0x7ff43fc4,0xfff02dff,0xea807dff, + 0x702cefff,0x25bffffd,0x00dfffeb,0x0b7fff66,0x3ff207f4,0xdd90fec0, + 0x37c47dfd,0x07f62fd8,0x6c357ff5,0x3fbbb21f,0xff99993e,0x7dc43999, + 0x2e0bffff,0x2dffffff,0x5dfffd70,0x3ff33320,0x7fd41ccc,0x666644ff, + 0x3e1cccff,0xffff983d,0xfcccc803,0x1331cccf,0x00026600,0x00cc0004, + 0x00100011,0x1dfb01fd,0x4f980fea,0x2fd837c4,0xfff707f5,0x980fea9f, + 0x3ffffe4f,0x3103ffff,0x00133001,0xffff8018,0x503fffff,0xffff87b9, + 0x003fffff,0x20019bd3,0xffffffff,0x0000003f,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x02ae6200, + 0xdddfd910,0xdd9501dd,0x0f223b9b,0x00014400,0x22179995,0x07ddb34e, + 0x23deedb8,0x0d4c02a8,0x55440055,0x4c40009a,0x551001ab,0x2aa35555, + 0xaaaaaaaa,0x5555530a,0x5554c555,0xaaaa8009,0x57510009,0x00aaa001, + 0xff5013ee,0xf101bfff,0xddffd9bf,0xfeeffd81,0x00df11ff,0x22001fe0, + 0x12fffffe,0xffdff5bf,0xddffd30b,0x103fe8ff,0x01fe21ff,0xffffffa8, + 0x7ffd401d,0x7e401eff,0xdf4fffff,0xdddddddd,0x3ffff21f,0x3ff67fff, + 0xf00dffff,0x07ffffff,0x1fffffe4,0x80bffe20,0xf702fff8,0x1dfd759f, + 0x27e43fd8,0x3fa06fe4,0x2000df11,0xdfd8007f,0x3fe21312,0x83ff30cf, + 0x26140dfe,0x23fd80ff,0x3ea007f8,0x3ffecbae,0x9339ff30,0xefef805f, + 0x021f1aaa,0x559f90f8,0x67ec5555,0x82ffecba,0xffecabff,0xa9adfd83, + 0x3fea03ff,0x0fffc04f,0x3a20ffc4,0x7c43fc3f,0x6c07fc47,0x000df11f, + 0x3fe001fe,0x213fe201,0x017f24fb,0x37cc4fd8,0x3bffffe2,0xb85fa81d, + 0x83fd80ff,0x2fdfd400,0x85dfd0f8,0xb007f90f,0x81ff705f,0x547fc87f, + 0x206f986f,0x4c07fafd,0x209f902c,0x21fe27fa,0x837dc7f8,0x00df11fd, + 0x3bffbba6,0xf301eeee,0x307f880d,0x000ff4bf,0x17f43ff1,0x3b733fe2, + 0x82fd43ff,0x00fe85fc,0x05f9fe80,0xf27dc41f,0x3600ff21,0xf8bfb02f, + 0x320ff887,0x105fb03f,0x0007faff,0x3fe01ff8,0xbf70bfa1,0x3fb04fc8, + 0x67ed5be2,0x7ffffcc1,0x302fffff,0x06f880bf,0x007fcdf3,0x2fd57ee0, + 0x7fd43fc4,0x7cc17ea1,0x98007f87,0x1f05f8cf,0x643e1f50,0x02fd803f, + 0x887f8ff3,0xb817ec7f,0xf74fa83f,0x03fc0009,0xcffa8bf6,0x7ec0ffda, + 0x3e23fb02,0x4ffeefce,0xf3001fe0,0x306f880b,0x001fe2df,0x407f57f4, + 0x49f907f8,0x03fe05fa,0x3f9000ff,0x203e0bf1,0x3f21f1f9,0x202fd803, + 0x321fe0ff,0xb81fec5f,0xf32fd84f,0x1be6000f,0x6ff47fb0,0xfc80dfff, + 0x3e23fd03,0x03fea4ff,0x7ffc03fc,0x7fffffff,0x27d41be2,0xf50001fd, + 0x1fe207ff,0xffeeb7d4,0xa8ffc1ee,0x2aaaaffa,0xeff8b7c0,0x4cc1f1ee, + 0x3f21f0fd,0x24eeeeee,0x87fe02fd,0xefecbbff,0x64077d40,0x3a3fc45f, + 0x6f98001f,0x4f99fe40,0x709f7002,0x0ffe23ff,0x03fc07fe,0x67fee664, + 0x1be24ccc,0x07f71fe4,0x881bf600,0x3ebf707f,0x742fffff,0xffffff1f, + 0x3ee01fff,0x3dddff13,0x06f7c43e,0x3ffff21f,0x0bf66fff,0x3ffe1fe8, + 0xfd00bfff,0x9fffb99f,0x27e45fa8,0x0ff30150,0x3df52fd8,0xa83fe200, + 0x0ff11fff,0x03fc0bf6,0xf1017e60,0x4c6fb88d,0x74040bff,0xeeeffeee, + 0x7f41fe21,0xff817ea3,0x333ff331,0x887f4033,0x3e21f05f,0x07f90f80, + 0x7fc05fb0,0x3f267fe1,0x3ffb200e,0x7ec3fccf,0xfe83fcc2,0x203fc40f, + 0xfffd11fe,0xfb05bfff,0x3fb9fd9f,0x17ec1be2,0x7cc007f8,0x677fc405, + 0xfc81fffc,0xe87ecdff,0xeeeffeee,0xf931fe21,0x882fd41f,0x003fc0ff, + 0x82fc4bf3,0x43e03a0f,0x2fd803fc,0x3fc1ff10,0x320013f2,0x267fe22f, + 0x441ff999,0x1ff82fff,0x7e41ff10,0x4fffeeed,0xfb3effc8,0x7ec1be23, + 0x9800ff02,0x7ffc405f,0x2e00dfff,0x405ffffe,0x3fe204fb,0x41efffee, + 0x0df705fa,0x7fe400ff,0x1f05ffff,0x7f90f804,0x2e05fb00,0x3e23fc6f, + 0x07f4000f,0xfffffff5,0x1dfb09ff,0x3ee09f90,0x7dc17ee5,0x23fb0207, + 0x05fb06f8,0xf98007fa,0x08b7c405,0x803be200,0x99dfc999,0x77fffc41, + 0x10bf503d,0x00ff07ff,0x3bbbbfe2,0x3ea1f05f,0x07f90f82,0xff105fb0, + 0x4fc87f85,0xfb0ff200,0xfb99999b,0xff88060d,0xfb07fd43,0x003fe205, + 0x06f88fec,0x13f605fb,0x4405f980,0x7f50006f,0xffffff80,0x1fe21fff, + 0x7545fa80,0x200ff06f,0x82fc43fb,0x1f03d30f,0x3f600ff2,0x7c2ffd42, + 0x400ff987,0x7fc46fd9,0x0007fe00,0x3fb3bfee,0xc837ec3f,0x47f6005f, + 0x05fb06f8,0x3733ffe6,0x880bf301,0x3f90006f,0xdfdaaa80,0x1fe20aaa, + 0xfeeffa80,0x7f6c0dff,0x3eeeeeef,0x9ff103fa,0x2003e599,0xddddf90f, + 0x777ecddd,0xff05fffe,0xddb13f60,0xfa819fff,0x0027ec05,0x1dfffea8, + 0xeeefff98,0x36000eff,0x360df11f,0x3ffaa02f,0x0bf301ff,0x30006f88, + 0x04fb8005,0xfa801fe2,0xf01cefff,0xffffffff,0x2217e69f,0xff5fffff, + 0xffffffff,0x3ffff21f,0x3ff67fff,0x3e01ceef,0x741ff307,0xfb01cdef, + 0x006fa807,0xeb880180,0x8002ceee,0x20df11ec,0x013002fd,0x74405f98, + 0x00000005,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0xaaaa8000,0xaaa98099,0x31aaaaaa,0x55555555,0x260aaa00, + 0x2055100a,0x2aa0d42a,0x0aaaaaaa,0x0aa60551,0x40335555,0x455302a9, + 0xaaaaaaa9,0x803551aa,0x02aa22a8,0x03530aa8,0x01551a88,0x0154c550, + 0x2aaaaa55,0x00aaaaaa,0x751002aa,0x80551015,0xfffffff8,0x3ffff20c, + 0xf95fffff,0x0fffffff,0xfb0fff70,0x7c1be205,0xfff0fe65,0x21ffffff, + 0x2ff886f8,0x3fffffe2,0x07fec0bf,0xfff737fc,0x2bffffff,0x2fe406fb, + 0x7fd413fa,0xf9807f50,0xfa809fb4,0x220fff26,0xffffff6f,0x501fffff, + 0x36203ffd,0x4c3fffff,0x57fc406f,0x2a5ffcba,0xdccccccc,0x555bf95f, + 0xff880555,0x102fd87f,0xfa93e0df,0x6fed5542,0x0df10aaa,0xaff887fd, + 0x6c5ffeba,0x3ffd43ff,0x99999993,0x81ffc9ff,0x7fcc0ff8,0xf517f441, + 0xf53f9809,0x323fc80f,0x56f886ff,0x55bfb555,0x7ff4c155,0x7bfd01ff, + 0x7cc3ff95,0x443fc406,0x3fd000ff,0x6c000ff2,0x2fd87fbf,0x9f10df10, + 0x9f700fdc,0x9fb1be20,0xff10ff10,0x3637f747,0xdf7007ee,0xfd80ffa8, + 0xfc8bf904,0x2a027cc5,0xf807fe3f,0x0bfbf21f,0x13ee0df1,0xff9dff98, + 0xbf905501,0x3e2037cc,0x3003fd07,0x001fe4df,0x43fc67d4,0x4df102fd, + 0xdaadfbaa,0x4fb81abf,0x2fdcdf10,0xbf907f88,0xf887e774,0xff8807dc, + 0x7cc4fe81,0x25ff100f,0x2fcc0ff9,0x4fd8bea0,0xbfc8df30,0xb837c46f, + 0xff0e404f,0x26f98003,0x3fc406f9,0x5fb007f8,0x22001fe4,0xd87f88ff, + 0x74df102f,0xffffffff,0x04fb85ff,0x03be6df1,0x6fa83fc4,0x7dcfe7ba, + 0x7ec00fd9,0x6c1ff304,0x47fd403f,0x45f883fe,0xfa8bee09,0xfc87f906, + 0x1be22fda,0x3e0027dc,0x37cc001f,0x7f880df3,0xf9804fc8,0x2001fe46, + 0xb0ff12fd,0x21be205f,0x701f61fb,0x23be209f,0x1fe201ff,0x3adf2fec, + 0x803f6dd6,0xa7ec06fa,0xdfb006f9,0xa9be20df,0xff07ee3f,0xfc81fd03, + 0x1be26faa,0x3e0027dc,0x2fdc001f,0xff880df3,0x002ffeee,0xcefc85fb, + 0xfa82cccc,0x3f61fe25,0xfeeeeeee,0x1ba0fc86,0x3e209f70,0x7c402fef, + 0x3e2ff887,0x367f5f95,0x03ff100f,0x2fd8ff88,0x03fff100,0x64df937c, + 0x2627ec1f,0xfd2fc86f,0x7dc1be23,0x00ffc004,0x7cc5fd10,0x7fffc406, + 0x4c02efff,0xffffc86f,0x0fe85fff,0x3ff61fe2,0x6fffffff,0x202fc7c8, + 0xfff104fb,0x9ff8805f,0x7c5ffdb9,0x321fff35,0x009fb01f,0x001bfbf2, + 0x37c01ffd,0x03f23fff,0x83fc8df5,0x22bf52fc,0x009f706f,0x36001ff8, + 0x2037cc5f,0x7fe447f8,0x320bf601,0x099999cf,0x1fe217e4,0x37c40bf6, + 0x7013e3ec,0x2bbe209f,0x3fe200ff,0x443fffff,0xfc97fa5f,0x2006fa81, + 0x2001fff8,0x3a05fffb,0x329f9f57,0x3a1ff80f,0x3e2fc80f,0xf706f89f, + 0x01ff8009,0xf981df90,0xc83fc406,0x20df305f,0xef9803fc,0x9ff99999, + 0x2205fb09,0xdffdd56f,0x20ddffdd,0x2df104fb,0xff100efb,0xf1013599, + 0x3f917dcb,0x4001ff88,0x3e6005fb,0xfd02ff9f,0x3edbe3f2,0x0df33fd8, + 0x8cfb8bf2,0x009f706f,0xfc801ff8,0x406f980e,0x0df507f8,0x1fe40ff6, + 0x7ffffdc0,0xb4ffffff,0x4dbe205f,0xfdccefcc,0x09f704cd,0x05fd9be2, + 0x3e600ff1,0x3617e405,0x4fb8004f,0x7dcffa00,0x5f8fd80f,0x2a0fb3f9, + 0x3207f76f,0x3e7fe22f,0x8009f706,0x77e401ff,0x880df300,0x309fb07f, + 0x01fe40ff,0x6666664c,0xfb2ccffc,0xf11be205,0x2e017d49,0x44df104f, + 0x07f883ff,0xfb809f30,0x0003bea2,0x7dc013ee,0x7e427f46,0xdd9f32fb, + 0x07f47fc0,0x67e42fc8,0x009f706f,0x3f201ff8,0x01be600e,0xffa88ff1, + 0x3203fd82,0x7c40003f,0xf102fd87,0x3ee4f88d,0x8827dc01,0x20ffcc6f, + 0x9f3007f8,0xff13fb80,0x13ee0003,0xf30bfe20,0x47efc83f,0x3f606eff, + 0x0bf206fc,0x2e0dfff1,0x0ffc004f,0x4c00efc8,0x77fc406f,0x983fffee, + 0x0ff200ff,0xb0ff1000,0x31be205f,0x6c07e47f,0xeeeffeee,0xff70df10, + 0xa803fc41,0xc9fdc04f,0xffffffff,0x013ee06f,0x37e417f6,0xff917fee, + 0x1fffd40b,0xff905f90,0xb013ee0d,0xdddffddd,0x3ffffe67,0xff36ffff, + 0x15dddddd,0x19dfffff,0xf900ff60,0x7f880007,0xdf102fd8,0x03f22fa8, + 0x3ffffffe,0x20df10ff,0x01fe26fd,0x3ee027d4,0xffffffb3,0x7dc0dfff, + 0x807fdc04,0x3fee4ff8,0x202ffcc2,0x3f200fff,0x706ff882,0xffff809f, + 0xf34fffff,0xffffffff,0x3ffffe6d,0x00003fff,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x80322000, + 0x8026200b,0x6c40001c,0xdb880000,0x2e01defe,0xe881cefd,0x6d43d905, + 0xdfd98cef,0x00054400,0x207bddb7,0x700cefdb,0xc83ddfdb,0x21bd505e, + 0xd52ed8bd,0xeeeed81b,0xaa986eee,0x2017540a,0x2e1d74e9,0x77441dfd, + 0x6c03b600,0x40df504f,0x7ff304fa,0x0ffe6000,0x7dc04fa8,0x42fffeef, + 0xfffeffe9,0xfd837c43,0x3bff7be2,0x7406fdef,0xffe9807f,0xefd87fee, + 0x7f42ffed,0x442feeef,0x17fc43ff,0xf3ffdb9f,0xfffe8bfd,0x7dc7ffff, + 0x3ea1ffff,0xfca7d404,0x1fffefc9,0x6fa81fec,0x32ebfe20,0x2a01ff9b, + 0x13fe604f,0x805ff700,0x20cc04fa,0x27f47fb8,0x6f887fea,0x36145fb0, + 0x405f90ff,0xdfe807fe,0x513f2140,0x417ee1ff,0x361ff980,0x5c77fc4f, + 0x200fd4ef,0xf70cc3fe,0x2e02fccb,0x45fdf93f,0x837d45fc,0x7fd403fd, + 0x403fffff,0x7f4404fa,0x1efc800d,0x0004fa80,0x82fd43fe,0x41be25fc, + 0x97ee02fd,0x072204fa,0xf100bf90,0x7e4ff20d,0xab7e4003,0xf52ff86f, + 0x32007ecf,0x37cc405f,0x9174cdf1,0x307ff23f,0x883ff0df,0x7fc400ff, + 0x402ffc9c,0xdfb004fa,0x03bf6201,0x00027d40,0x40bf23fb,0x41be26f8, + 0x8fea02fd,0xe80004f9,0x04fa801f,0x0bfee9f5,0x1ff9fd00,0xb27d4ff0, + 0x077d401f,0x37fff644,0x365fc9fe,0xd107f90f,0xfb89f90b,0x645fb804, + 0x7777645f,0x205eeeff,0x7f441ffb,0x5ccccc04,0x981999df,0x1ffffeec, + 0x13fc03fd,0x88bf60df,0xeeefeedb,0x266665fe,0x41999999,0xefb800ff, + 0x5feeeeee,0x0077fff6,0x7c0bffe2,0x0fd8fea7,0x3203ff30,0x746f99af, + 0x3f43ffa7,0xf88005f9,0x6c00ff47,0x363fcc2f,0xffffffff,0x8ffdc07f, + 0xff805ff8,0xffffffff,0x33bfee1f,0x3fd1ffcc,0x0df13fc0,0xcffe8bf6, + 0x2ccccefd,0x3ffffffe,0xff11ffff,0x3bbbf200,0x4c4eeeee,0x200efffd, + 0xff00ffe8,0x01fb1fd4,0x37c05fd1,0x98fd8df5,0x64df3fcf,0x2fd8002f, + 0x3f600df3,0x2a03fc42,0x3fe6004f,0x201ffd43,0xcefdcccc,0x3ff10ccc, + 0x0bf63fb0,0x0df137c4,0x52fd4bf6,0xcccc809f,0x0ccccccc,0x3ee003fe, + 0xfd510005,0x77f7ec0d,0x8fea7f80,0x04fd80fd,0x37ff6fec,0x3e3f27ee, + 0x017e4bf6,0x3fcafd40,0xf989fb00,0x8027d406,0xfd302ffb,0x027d4009, + 0x47fa0bf5,0x8bf704fc,0x97fc40ff,0x01bea2fc,0x01ff4000,0x00007fd4, + 0xdf701ff1,0xa9fe17f6,0xf703f63f,0x17b7100d,0x5ebfa877,0x7e49f5f9, + 0xd1ff0002,0x3bea001f,0xf500ff60,0x03df9009,0x800dfe88,0x1bea04fa, + 0x3e23ffb1,0x20ffcc1f,0x3ffa22ff,0x3fa27f72,0x0054001f,0x8102ffea, + 0x30000cfe,0x23ff30ff,0x53fc3ff8,0xf307ec7f,0x4c00001f,0xfbf32fdf, + 0x80017e47,0x2005fdfc,0xfffdfff8,0x1027d401,0x6c001dfb,0x27d400ef, + 0xfb9dff10,0x7fdc3fdf,0xb83ffdcd,0xfdefcdff,0x7dd9df12,0x403b99ff, + 0xffd806fd,0x7cc7ecdf,0x0eccbdff,0xffb999dd,0x4c3ff889,0xfa9fe1ff, + 0xfff83f63,0x32eeeeee,0x7ddddddd,0xff07ffc4,0x0017e45f,0x002fff98, + 0x37ffbbf6,0x8164c06f,0x70005fe8,0x27d403ff,0x37fffa60,0x7f541fc8, + 0x3aa01eff,0x22fb8dff,0xff90efeb,0x3ff403ff,0xffffea80,0xffffd885, + 0xffffb0ef,0x0bfb05df,0x53fc3ff2,0xff07ec7f,0x5fffffff,0x3bbbbba6, + 0x322ffc3e,0x00bf20ff,0x2006fe80,0x09fb06fb,0x001f4400,0x98807ea0, + 0x00011000,0x00088003,0x26002601,0x0088002c,0x4cc01310,0x00000009, + 0x00000000,0x00000000,0x4407ee00,0x3333264f,0x002ccccc,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x40000000, + 0x3fee0400,0x4fffffff,0x74400000,0x4039fb54,0x64400aa9,0xb9800001, + 0x000001bc,0x03372a00,0x4017bb93,0x16dc04c9,0x200e65c4,0x333100a8, + 0x81333333,0x4cc40bb9,0x09999999,0x26057700,0x257714c2,0x00000bb9, + 0x40000000,0xfeefcdf8,0x7fff444f,0x80be202f,0xc81d705c,0x40dfdcdf, + 0x3203645c,0xfd83320d,0x3f21ffff,0x6cc0ffff,0x3fe207ff,0x3fffea0f, + 0x41bf604f,0xfffffffa,0x0efb84ff,0x3ffffff2,0xfd802fff,0x11ff880d, + 0x54ffa3ff,0x000000ff,0x44000000,0x3fea3fff,0x3ee6ff60,0x8fc46a0f, + 0x717fa238,0x557641df,0x7ec5d88a,0x7d40ff23,0x1731be65,0x32049fb1, + 0x3f7fe23f,0x897ffc07,0x05fd10ff,0x5107fbf5,0x55555555,0x543be603, + 0xebbbbbbb,0x01fec02f,0xd1fe83fa,0x001fe67f,0x00000000,0x3e0ffe20, + 0xfc8bf31f,0x5cfd3fa3,0x57fa20ff,0x27e60efb,0x3f8afcfa,0x0fe83fa2, + 0x07fa0fe8,0x7ec0bf70,0x03fc45c2,0x1fd4bfea,0x75ba13ea,0x8800000f, + 0x05f90009,0x808004c4,0x3fccbf60,0x00000000,0x83fc4000,0xa87f52fd, + 0x77f7544f,0xefe880bf,0x6ab5c0ef,0xbf317299,0x8ff22fcc,0x7f4403fc, + 0x027ff542,0xff880ff1,0x4f987f70,0x44f98fdc,0x99999998,0x20000099, + 0x000002fc,0x37c4bf60,0x00000000,0x837c4000,0xb8bf32fd,0x1ffe883f, + 0x407ff440,0x21ddf54d,0x362fc86b,0x7ccdf12f,0x42ff4406,0x103ffdc9, + 0x25fc80ff,0x117e45f9,0x2a1fd8bf,0xffffffff,0x3200004f,0x0000002f, + 0x037c47f2,0x00000000,0xd837c400,0x223ff12f,0x37f220fe,0xf701dfdf, + 0x2ab90bff,0x88b90fae,0xd8df10ff,0x5407f62f,0x7f9804ff,0xd910ff10, + 0xbdfe81df,0x207e46fd,0x2eee66f8,0x02bbbbbb,0x00000000,0x00000000, + 0x00000000,0x17ec1be2,0x87ffdff7,0xfd33f2fe,0xfe8efb81,0xdb547d45, + 0x22fd89d4,0x137c42fd,0xbffb81df,0xff700999,0x3a61fe20,0xfffb102d, + 0xb827cc19,0x6d40003f,0x2ca8103d,0xffb80dcc,0x55534fff,0x00000555, + 0x00000000,0x7ec1be20,0x40e6e4c2,0x20c3f109,0xbfd10efb,0x99339dd8, + 0xf527dc1f,0xfb93ee0b,0x3ffffe25,0xffddb2ff,0xffffe85f,0x010001ff, + 0x00130062,0x2ffffdc0,0x7ffccbee,0x503fff11,0x3a799999,0x007fffff, + 0x00000000,0x0df10000,0x10000bf6,0x2077405f,0x3ba20fe8,0x741fda9b, + 0xfd007f46,0x999076c1,0x3ae39999,0xccb80bde,0x0000cccc,0x80000000, + 0x8dfd89fe,0xfff50fd8,0x00fffe65,0x333332e0,0x00000004,0x10000000, + 0x4cbf60df,0x3eeeeeee,0x04401510,0xdfb70088,0x00202019,0x00000101, + 0x00000000,0x7c400000,0x2ffffec5,0x1ffd1bfa,0x00000000,0x00000000, + 0x20df1000,0xdddd32fd,0x00007ddd,0x00000000,0x00000000,0x00000000, + 0x06a00000,0x80413bae,0x00000009,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, +}; + +static signed short stb__consolas_24_latin1_x[224]={ 0,5,3,0,1,0,0,5,3,3,1,0,2,3, +4,1,1,1,1,1,0,2,1,1,1,1,4,2,1,1,2,3,0,0,1,1,1,2,2,0,1,2,2,1, +2,0,1,0,1,0,1,1,1,1,0,0,0,0,1,4,1,3,1,0,0,1,1,1,1,1,0,1,1,2, +1,2,2,1,1,1,1,1,2,2,0,1,0,0,0,0,1,1,5,2,0,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,5,1,1,1,0, +5,1,0,0,2,1,1,3,1,0,2,1,2,3,0,1,1,4,5,2,2,1,0,0,0,2,0,0,0,0, +0,0,-1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0, +0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, + }; +static signed short stb__consolas_24_latin1_y[224]={ 17,0,0,1,-1,0,0,0,-1,-1,0,4,13,9, +13,0,1,1,1,1,1,1,1,1,1,1,5,5,4,7,4,0,0,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,20,0,5,0,5,0,5,0,5,0,0, +0,0,0,5,5,5,5,5,5,5,1,5,5,5,5,5,5,0,-3,0,8,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,17,5,-1,1,2,1, +-3,0,0,1,1,5,9,9,0,1,0,2,0,0,0,5,0,8,17,0,1,5,0,0,0,5,-3,-3,-3,-3, +-3,-4,1,1,-3,-3,-3,-3,-3,-3,-3,-3,1,-3,-3,-3,-3,-3,-3,5,-1,-3,-3,-3,-3,-3,1,0,0,0, +0,0,0,-1,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,2,0,0,0,0,0,0,0, + }; +static unsigned short stb__consolas_24_latin1_w[224]={ 0,4,8,13,11,13,14,3,8,7,11,13,7,8, +5,11,12,11,11,11,13,10,11,11,11,11,5,7,10,11,10,8,14,14,11,11,12,10,10,12,11,10,9,12, +10,13,11,13,11,14,12,11,12,11,14,13,13,14,11,6,11,7,11,14,8,11,11,11,11,11,13,12,11,10, +10,11,10,12,11,12,11,11,11,10,12,11,13,13,13,13,11,10,3,10,13,12,12,12,12,12,12,12,12,12, +12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0,4,10,11,12,13, +3,11,11,14,9,11,11,8,11,10,9,11,9,8,12,12,11,5,3,9,9,11,13,13,13,8,14,14,14,14, +14,14,14,11,12,12,12,12,12,12,12,12,13,12,13,13,13,13,13,11,13,12,12,12,12,14,11,11,12,12, +12,12,12,12,13,11,12,12,12,12,12,12,12,12,11,12,13,13,13,13,13,13,12,12,12,12,12,13,11,13, + }; +static unsigned short stb__consolas_24_latin1_h[224]={ 0,18,6,16,21,18,18,6,23,23,11,13,9,3, +5,20,17,16,16,17,16,17,17,16,17,16,13,17,14,7,14,18,22,16,16,17,16,16,16,17,16,16,17,16, +16,16,16,17,16,21,16,17,16,17,16,16,16,16,16,22,20,22,9,2,6,13,18,13,18,13,17,17,17,17, +22,17,17,12,12,13,17,17,12,13,17,13,12,12,12,17,12,22,25,22,5,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,0,17,21,16,15,16, +25,20,6,17,12,11,6,3,11,5,9,15,10,10,6,17,20,5,4,10,12,11,17,17,17,17,20,20,20,20, +20,21,16,20,20,20,20,20,20,20,20,20,16,20,21,21,21,21,21,11,21,21,21,21,21,20,16,18,18,18, +18,18,18,19,13,16,18,18,18,18,17,17,17,17,18,17,18,18,18,18,18,13,18,18,18,18,18,22,22,22, + }; +static unsigned short stb__consolas_24_latin1_s[224]={ 252,250,247,62,40,106,104,252,17,9,70, +48,159,238,215,91,183,221,233,12,36,1,222,13,152,220,247,223,37,189,26, +40,100,232,1,24,194,183,25,36,50,76,49,87,245,112,196,1,100,129,207, +164,208,176,181,167,153,138,126,34,146,26,177,26,201,62,119,127,171,139,65, +15,40,245,89,74,141,176,48,74,79,28,225,151,52,87,237,211,162,231,189, +41,1,64,201,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170, +170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,252,247,157, +143,1,103,5,186,235,59,201,118,210,238,94,227,167,14,130,140,222,196,79, +221,252,149,60,106,86,113,127,201,198,213,66,118,103,52,155,67,133,173,14, +27,241,1,40,53,129,228,168,182,196,210,224,82,238,1,14,27,144,158,117, +94,172,185,198,211,131,81,99,91,133,159,146,120,234,209,210,188,224,100,236, +49,157,90,63,113,144,27,1,77,14,75,52,115, }; +static unsigned short stb__consolas_24_latin1_t[224]={ 13,49,156,125,27,49,70,1,1,1,156, +142,156,163,163,27,70,125,125,89,125,89,70,125,89,107,107,89,142,156,142, +70,1,107,125,89,107,107,125,89,125,125,89,125,125,125,125,107,125,1,107, +89,125,89,125,125,125,125,125,1,27,1,156,24,156,142,70,142,70,142,107, +107,107,89,1,89,89,142,156,142,107,107,142,142,107,142,142,142,142,89,142, +1,1,1,163,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107, +107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,13,70,1, +107,142,107,1,27,156,89,142,156,156,163,156,163,156,142,156,156,156,70,27, +163,8,156,156,156,89,89,89,89,27,27,49,27,27,27,107,27,27,27,49, +49,27,49,49,49,107,27,1,1,1,1,1,156,1,27,27,27,1,27,107, +49,49,49,49,49,70,49,142,107,49,49,49,49,70,70,89,89,49,89,49, +70,70,70,70,142,70,70,70,70,70,1,1,1, }; +static unsigned short stb__consolas_24_latin1_a[224]={ 211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211, }; + +// Call this function with +// font: NULL or array length +// data: NULL or specified size +// height: STB_FONT_consolas_24_latin1_BITMAP_HEIGHT or STB_FONT_consolas_24_latin1_BITMAP_HEIGHT_POW2 +// return value: spacing between lines +static void stb_font_consolas_24_latin1(stb_fontchar font[STB_FONT_consolas_24_latin1_NUM_CHARS], + unsigned char data[STB_FONT_consolas_24_latin1_BITMAP_HEIGHT][STB_FONT_consolas_24_latin1_BITMAP_WIDTH], + int height) +{ + int i,j; + if (data != 0) { + unsigned int *bits = stb__consolas_24_latin1_pixels; + unsigned int bitpack = *bits++, numbits = 32; + for (i=0; i < STB_FONT_consolas_24_latin1_BITMAP_WIDTH*height; ++i) + data[0][i] = 0; // zero entire bitmap + for (j=1; j < STB_FONT_consolas_24_latin1_BITMAP_HEIGHT-1; ++j) { + for (i=1; i < STB_FONT_consolas_24_latin1_BITMAP_WIDTH-1; ++i) { + unsigned int value; + if (numbits==0) bitpack = *bits++, numbits=32; + value = bitpack & 1; + bitpack >>= 1, --numbits; + if (value) { + if (numbits < 3) bitpack = *bits++, numbits = 32; + data[j][i] = (bitpack & 7) * 0x20 + 0x1f; + bitpack >>= 3, numbits -= 3; + } else { + data[j][i] = 0; + } + } + } + } + + // build font description + if (font != 0) { + float recip_width = 1.0f / STB_FONT_consolas_24_latin1_BITMAP_WIDTH; + float recip_height = 1.0f / height; + for (i=0; i < STB_FONT_consolas_24_latin1_NUM_CHARS; ++i) { + // pad characters so they bilerp from empty space around each character + font[i].s0 = (stb__consolas_24_latin1_s[i]) * recip_width; + font[i].t0 = (stb__consolas_24_latin1_t[i]) * recip_height; + font[i].s1 = (stb__consolas_24_latin1_s[i] + stb__consolas_24_latin1_w[i]) * recip_width; + font[i].t1 = (stb__consolas_24_latin1_t[i] + stb__consolas_24_latin1_h[i]) * recip_height; + font[i].x0 = stb__consolas_24_latin1_x[i]; + font[i].y0 = stb__consolas_24_latin1_y[i]; + font[i].x1 = stb__consolas_24_latin1_x[i] + stb__consolas_24_latin1_w[i]; + font[i].y1 = stb__consolas_24_latin1_y[i] + stb__consolas_24_latin1_h[i]; + font[i].advance_int = (stb__consolas_24_latin1_a[i]+8)>>4; + font[i].s0f = (stb__consolas_24_latin1_s[i] - 0.5f) * recip_width; + font[i].t0f = (stb__consolas_24_latin1_t[i] - 0.5f) * recip_height; + font[i].s1f = (stb__consolas_24_latin1_s[i] + stb__consolas_24_latin1_w[i] + 0.5f) * recip_width; + font[i].t1f = (stb__consolas_24_latin1_t[i] + stb__consolas_24_latin1_h[i] + 0.5f) * recip_height; + font[i].x0f = stb__consolas_24_latin1_x[i] - 0.5f; + font[i].y0f = stb__consolas_24_latin1_y[i] - 0.5f; + font[i].x1f = stb__consolas_24_latin1_x[i] + stb__consolas_24_latin1_w[i] + 0.5f; + font[i].y1f = stb__consolas_24_latin1_y[i] + stb__consolas_24_latin1_h[i] + 0.5f; + font[i].advance = stb__consolas_24_latin1_a[i]/16.0f; + } + } +} + +#ifndef STB_SOMEFONT_CREATE +#define STB_SOMEFONT_CREATE stb_font_consolas_24_latin1 +#define STB_SOMEFONT_BITMAP_WIDTH STB_FONT_consolas_24_latin1_BITMAP_WIDTH +#define STB_SOMEFONT_BITMAP_HEIGHT STB_FONT_consolas_24_latin1_BITMAP_HEIGHT +#define STB_SOMEFONT_BITMAP_HEIGHT_POW2 STB_FONT_consolas_24_latin1_BITMAP_HEIGHT_POW2 +#define STB_SOMEFONT_FIRST_CHAR STB_FONT_consolas_24_latin1_FIRST_CHAR +#define STB_SOMEFONT_NUM_CHARS STB_FONT_consolas_24_latin1_NUM_CHARS +#define STB_SOMEFONT_LINE_SPACING STB_FONT_consolas_24_latin1_LINE_SPACING +#endif + diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index db6598c43d..ccb91590c3 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -157,10 +157,6 @@ protected: //static const wchar_t* EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y"; static const glm::uvec2 QUAD_OFFSET(10, 10); -static const glm::vec3 COLORS[4] = { { 1.0, 1.0, 1.0 }, { 0.5, 1.0, 0.5 }, { - 1.0, 0.5, 0.5 }, { 0.5, 0.5, 1.0 } }; - - void testShaderBuild(const char* vs_src, const char * fs_src) { auto vs = gpu::Shader::createVertex(std::string(vs_src)); auto fs = gpu::Shader::createPixel(std::string(fs_src)); diff --git a/tests/shared/src/GLMHelpersTests.cpp b/tests/shared/src/GLMHelpersTests.cpp index afb634ecbd..a796d62ba5 100644 --- a/tests/shared/src/GLMHelpersTests.cpp +++ b/tests/shared/src/GLMHelpersTests.cpp @@ -54,4 +54,51 @@ void GLMHelpersTests::testEulerDecomposition() { } } +static void testQuatCompression(glm::quat testQuat) { + float MAX_COMPONENT_ERROR = 4.3e-5f; + + glm::quat q; + uint8_t bytes[6]; + packOrientationQuatToSixBytes(bytes, testQuat); + unpackOrientationQuatFromSixBytes(bytes, q); + if (glm::dot(q, testQuat) < 0.0f) { + q = -q; + } + QCOMPARE_WITH_ABS_ERROR(q.x, testQuat.x, MAX_COMPONENT_ERROR); + QCOMPARE_WITH_ABS_ERROR(q.y, testQuat.y, MAX_COMPONENT_ERROR); + QCOMPARE_WITH_ABS_ERROR(q.z, testQuat.z, MAX_COMPONENT_ERROR); + QCOMPARE_WITH_ABS_ERROR(q.w, testQuat.w, MAX_COMPONENT_ERROR); +} + +void GLMHelpersTests::testSixByteOrientationCompression() { + 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)); + + testQuatCompression(ROT_X_90); + testQuatCompression(ROT_Y_180); + testQuatCompression(ROT_Z_30); + testQuatCompression(ROT_X_90 * ROT_Y_180); + testQuatCompression(ROT_Y_180 * ROT_X_90); + testQuatCompression(ROT_Z_30 * ROT_X_90); + testQuatCompression(ROT_X_90 * ROT_Z_30); + testQuatCompression(ROT_Z_30 * ROT_Y_180); + testQuatCompression(ROT_Y_180 * ROT_Z_30); + testQuatCompression(ROT_X_90 * ROT_Y_180 * ROT_Z_30); + testQuatCompression(ROT_Y_180 * ROT_Z_30 * ROT_X_90); + testQuatCompression(ROT_Z_30 * ROT_X_90 * ROT_Y_180); + + testQuatCompression(-ROT_X_90); + testQuatCompression(-ROT_Y_180); + testQuatCompression(-ROT_Z_30); + testQuatCompression(-(ROT_X_90 * ROT_Y_180)); + testQuatCompression(-(ROT_Y_180 * ROT_X_90)); + testQuatCompression(-(ROT_Z_30 * ROT_X_90)); + testQuatCompression(-(ROT_X_90 * ROT_Z_30)); + testQuatCompression(-(ROT_Z_30 * ROT_Y_180)); + testQuatCompression(-(ROT_Y_180 * ROT_Z_30)); + testQuatCompression(-(ROT_X_90 * ROT_Y_180 * ROT_Z_30)); + testQuatCompression(-(ROT_Y_180 * ROT_Z_30 * ROT_X_90)); + testQuatCompression(-(ROT_Z_30 * ROT_X_90 * ROT_Y_180)); +} diff --git a/tests/shared/src/GLMHelpersTests.h b/tests/shared/src/GLMHelpersTests.h index 5e880899e8..40d552a07b 100644 --- a/tests/shared/src/GLMHelpersTests.h +++ b/tests/shared/src/GLMHelpersTests.h @@ -19,6 +19,7 @@ class GLMHelpersTests : public QObject { Q_OBJECT private slots: void testEulerDecomposition(); + void testSixByteOrientationCompression(); }; float getErrorDifference(const float& a, const float& b); diff --git a/tests/ui/qml/Stubs.qml b/tests/ui/qml/Stubs.qml index 8f828a0186..8c1465d54c 100644 --- a/tests/ui/qml/Stubs.qml +++ b/tests/ui/qml/Stubs.qml @@ -23,11 +23,23 @@ Item { function getUsername() { return "Jherico"; } } + Item { + objectName: "GL" + property string vendor: "" + } + Item { objectName: "ApplicationCompositor" property bool reticleOverDesktop: true } + Item { + objectName: "Controller" + function getRecommendedOverlayRect() { + return Qt.rect(0, 0, 1920, 1080); + } + } + Item { objectName: "Preferences" // List of categories obtained by logging categories as they are added in Interface in Preferences::addPreference(). diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index e45749e1de..8ca9399b74 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -5,7 +5,7 @@ import Qt.labs.settings 1.0 import "../../../interface/resources/qml" //import "../../../interface/resources/qml/windows" -import "../../../interface/resources/qml/windows-uit" +import "../../../interface/resources/qml/windows" import "../../../interface/resources/qml/dialogs" import "../../../interface/resources/qml/hifi" import "../../../interface/resources/qml/hifi/dialogs" @@ -17,260 +17,346 @@ ApplicationWindow { width: 1280 height: 800 title: qsTr("Scratch App") + toolBar: Row { + id: testButtons + anchors { margins: 8; left: parent.left; top: parent.top } + spacing: 8 + property int count: 0 + + property var tabs: []; + property var urls: []; + property var toolbar; + property var lastButton; + + Button { + text: HMD.active ? "Disable HMD" : "Enable HMD" + onClicked: HMD.active = !HMD.active + } + + Button { + text: desktop.hmdHandMouseActive ? "Disable HMD HandMouse" : "Enable HMD HandMouse" + onClicked: desktop.hmdHandMouseActive = !desktop.hmdHandMouseActive + } + + // Window visibility + Button { + text: "toggle desktop" + onClicked: desktop.togglePinned() + } + + Button { + text: "Create Toolbar" + onClicked: testButtons.toolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); + } + + Button { + text: "Toggle Toolbar Direction" + onClicked: testButtons.toolbar.horizontal = !testButtons.toolbar.horizontal + } + + Button { + readonly property var icons: [ + "edit-01.svg", + "model-01.svg", + "cube-01.svg", + "sphere-01.svg", + "light-01.svg", + "text-01.svg", + "web-01.svg", + "zone-01.svg", + "particle-01.svg", + ] + property int iconIndex: 0 + readonly property string toolIconUrl: "../../../../../scripts/system/assets/images/tools/" + text: "Create Button" + onClicked: { + var name = icons[iconIndex]; + var url = toolIconUrl + name; + iconIndex = (iconIndex + 1) % icons.length; + var button = testButtons.lastButton = testButtons.toolbar.addButton({ + imageURL: url, + objectName: name, + subImage: { + y: 50, + }, + alpha: 0.9 + }); + + button.clicked.connect(function(){ + console.log("Clicked on button " + button.imageURL + " alpha " + button.alpha) + }); + } + } + + Button { + text: "Toggle Button Visible" + onClicked: testButtons.lastButton.visible = !testButtons.lastButton.visible + } + + + + + // Error alerts + /* + Button { + // Message without title. + text: "Show Error" + onClicked: { + var messageBox = desktop.messageBox({ + text: "Diagnostic cycle will be complete in 30 seconds", + icon: hifi.icons.critical, + }); + messageBox.selected.connect(function(button) { + console.log("You clicked " + button) + }) + } + } + Button { + // detailedText is not currently used anywhere in Interface but it is easier to leave in and style good enough. + text: "Show Long Error" + onClicked: { + desktop.messageBox({ + informativeText: "Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds ", + text: "Baloney", + icon: hifi.icons.warning, + detailedText: "sakjd;laskj dksa;dl jka;lsd j;lkjas ;dlkaj s;dlakjd ;alkjda; slkjda; lkjda;lksjd ;alksjd; alksjd ;alksjd; alksjd; alksdjas;ldkjas;lkdja ;kj ;lkasjd; lkj as;dlka jsd;lka jsd;laksjd a" + }); + } + } + */ + + // query + /* + // There is no such desktop.queryBox() function; may need to update test to cover QueryDialog.qml? + Button { + text: "Show Query" + onClicked: { + var queryBox = desktop.queryBox({ + text: "Have you stopped beating your wife?", + placeholderText: "Are you sure?", + // icon: hifi.icons.critical, + }); + queryBox.selected.connect(function(result) { + console.log("User responded with " + result); + }); + + queryBox.canceled.connect(function() { + console.log("User cancelled query box "); + }) + } + } + */ + + // Browser + /* + Button { + text: "Open Browser" + onClicked: builder.createObject(desktop); + property var builder: Component { + Browser {} + } + } + */ + + + // file dialog + /* + + Button { + text: "Open Directory" + property var builder: Component { + FileDialog { selectDirectory: true } + } + + onClicked: { + var fileDialog = builder.createObject(desktop); + fileDialog.canceled.connect(function(){ + console.log("Cancelled") + }) + fileDialog.selectedFile.connect(function(file){ + console.log("Selected " + file) + }) + } + } + Button { + text: "Open File" + property var builder: Component { + FileDialog { + title: "Open File" + filter: "All Files (*.*)" + //filter: "HTML files (*.html);;Other(*.png)" + } + } + + onClicked: { + var fileDialog = builder.createObject(desktop); + fileDialog.canceled.connect(function(){ + console.log("Cancelled") + }) + fileDialog.selectedFile.connect(function(file){ + console.log("Selected " + file) + }) + } + } + */ + + // tabs + /* + Button { + text: "Add Tab" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo" }); + desktop.toolWindow.showTabForUrl("Foo", true); + } + } + + Button { + text: "Add Tab 2" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo 2" }); + desktop.toolWindow.showTabForUrl("Foo 2", true); + } + } + + Button { + text: "Add Tab 3" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo 3" }); + desktop.toolWindow.showTabForUrl("Foo 3", true); + } + } + + Button { + text: "Destroy Tab" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.removeTabForUrl("Foo"); + } + } + */ + + // Hifi specific stuff + /* + Button { + // Shows the dialog with preferences sections but not each section's preference items + // because Preferences.preferencesByCategory() method is not stubbed out. + text: "Settings > General..." + property var builder: Component { + GeneralPreferencesDialog { } + } + onClicked: { + var runningScripts = builder.createObject(desktop); + } + } + + Button { + text: "Running Scripts" + property var builder: Component { + RunningScripts { } + } + onClicked: { + var runningScripts = builder.createObject(desktop); + } + } + + Button { + text: "Attachments" + property var builder: Component { + AttachmentsDialog { } + } + onClicked: { + var attachmentsDialog = builder.createObject(desktop); + } + } + Button { + // Replicates message box that pops up after selecting new avatar. Includes title. + text: "Confirm Avatar" + onClicked: { + var messageBox = desktop.messageBox({ + title: "Set Avatar", + text: "Would you like to use 'Albert' for your avatar?", + icon: hifi.icons.question, // Test question icon + //icon: hifi.icons.information, // Test informaton icon + //icon: hifi.icons.warning, // Test warning icon + //icon: hifi.icons.critical, // Test critical icon + //icon: hifi.icons.none, // Test no icon + buttons: OriginalDialogs.StandardButton.Ok + OriginalDialogs.StandardButton.Cancel, + defaultButton: OriginalDialogs.StandardButton.Ok + }); + messageBox.selected.connect(function(button) { + console.log("You clicked " + button) + }) + } + } + */ + // bookmarks + /* + Button { + text: "Bookmark Location" + onClicked: { + desktop.inputDialog({ + title: "Bookmark Location", + icon: hifi.icons.placemark, + label: "Name" + }); + } + } + Button { + text: "Delete Bookmark" + onClicked: { + desktop.inputDialog({ + title: "Delete Bookmark", + icon: hifi.icons.placemark, + label: "Select the bookmark to delete", + items: ["Bookmark A", "Bookmark B", "Bookmark C"] + }); + } + } + Button { + text: "Duplicate Bookmark" + onClicked: { + desktop.messageBox({ + title: "Duplicate Bookmark", + icon: hifi.icons.warning, + text: "The bookmark name you entered alread exists in yoru list.", + informativeText: "Would you like to overwrite it?", + buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, + defaultButton: OriginalDialogs.StandardButton.Yes + }); + } + } + */ + + } + HifiConstants { id: hifi } Desktop { id: desktop anchors.fill: parent - rootMenu: StubMenu { id: rootMenu } + + //rootMenu: StubMenu { id: rootMenu } //Component.onCompleted: offscreenWindow = appWindow + /* MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: desktop.popupMenu(Qt.vector2d(mouseX, mouseY)); } + */ - Row { - id: testButtons - anchors { margins: 8; left: parent.left; top: parent.top } - spacing: 8 - property int count: 0 - - property var tabs: []; - property var urls: []; - - Button { - // Shows the dialog with preferences sections but not each section's preference items - // because Preferences.preferencesByCategory() method is not stubbed out. - text: "Settings > General..." - property var builder: Component { - GeneralPreferencesDialog { } - } - onClicked: { - var runningScripts = builder.createObject(desktop); - } - } - - Button { - text: "Running Scripts" - property var builder: Component { - RunningScripts { } - } - onClicked: { - var runningScripts = builder.createObject(desktop); - } - } - - Button { - text: "Attachments" - property var builder: Component { - AttachmentsDialog { } - } - onClicked: { - var attachmentsDialog = builder.createObject(desktop); - } - } - - /* - Button { - text: "restore all" - onClicked: { - for (var i = 0; i < desktop.windows.length; ++i) { - desktop.windows[i].visible = true - } - } - } - Button { - text: "toggle blue visible" - onClicked: { - blue.visible = !blue.visible - } - } - Button { - text: "toggle blue enabled" - onClicked: { - blue.enabled = !blue.enabled - } - } - */ - Button { - // Replicates message box that pops up after selecting new avatar. Includes title. - text: "Confirm Avatar" - onClicked: { - var messageBox = desktop.messageBox({ - title: "Set Avatar", - text: "Would you like to use 'Albert' for your avatar?", - icon: hifi.icons.question, // Test question icon - //icon: hifi.icons.information, // Test informaton icon - //icon: hifi.icons.warning, // Test warning icon - //icon: hifi.icons.critical, // Test critical icon - //icon: hifi.icons.none, // Test no icon - buttons: OriginalDialogs.StandardButton.Ok + OriginalDialogs.StandardButton.Cancel, - defaultButton: OriginalDialogs.StandardButton.Ok - }); - messageBox.selected.connect(function(button) { - console.log("You clicked " + button) - }) - } - } - Button { - // Message without title. - text: "Show Error" - onClicked: { - var messageBox = desktop.messageBox({ - text: "Diagnostic cycle will be complete in 30 seconds", - icon: hifi.icons.critical, - }); - messageBox.selected.connect(function(button) { - console.log("You clicked " + button) - }) - } - } - Button { - // detailedText is not currently used anywhere in Interface but it is easier to leave in and style good enough. - text: "Show Long Error" - onClicked: { - desktop.messageBox({ - informativeText: "Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds ", - text: "Baloney", - icon: hifi.icons.warning, - detailedText: "sakjd;laskj dksa;dl jka;lsd j;lkjas ;dlkaj s;dlakjd ;alkjda; slkjda; lkjda;lksjd ;alksjd; alksjd ;alksjd; alksjd; alksdjas;ldkjas;lkdja ;kj ;lkasjd; lkj as;dlka jsd;lka jsd;laksjd a" - }); - } - } - Button { - text: "Bookmark Location" - onClicked: { - desktop.inputDialog({ - title: "Bookmark Location", - icon: hifi.icons.placemark, - label: "Name" - }); - } - } - Button { - text: "Delete Bookmark" - onClicked: { - desktop.inputDialog({ - title: "Delete Bookmark", - icon: hifi.icons.placemark, - label: "Select the bookmark to delete", - items: ["Bookmark A", "Bookmark B", "Bookmark C"] - }); - } - } - Button { - text: "Duplicate Bookmark" - onClicked: { - desktop.messageBox({ - title: "Duplicate Bookmark", - icon: hifi.icons.warning, - text: "The bookmark name you entered alread exists in yoru list.", - informativeText: "Would you like to overwrite it?", - buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, - defaultButton: OriginalDialogs.StandardButton.Yes - }); - } - } - /* - // There is no such desktop.queryBox() function; may need to update test to cover QueryDialog.qml? - Button { - text: "Show Query" - onClicked: { - var queryBox = desktop.queryBox({ - text: "Have you stopped beating your wife?", - placeholderText: "Are you sure?", - // icon: hifi.icons.critical, - }); - queryBox.selected.connect(function(result) { - console.log("User responded with " + result); - }); - - queryBox.canceled.connect(function() { - console.log("User cancelled query box "); - }) - } - } - */ - Button { - text: "Open Directory" - property var builder: Component { - FileDialog { selectDirectory: true } - } - - onClicked: { - var fileDialog = builder.createObject(desktop); - fileDialog.canceled.connect(function(){ - console.log("Cancelled") - }) - fileDialog.selectedFile.connect(function(file){ - console.log("Selected " + file) - }) - } - } - - Button { - text: "Add Tab" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo" }); - desktop.toolWindow.showTabForUrl("Foo", true); - } - } - - Button { - text: "Add Tab 2" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo 2" }); - desktop.toolWindow.showTabForUrl("Foo 2", true); - } - } - - Button { - text: "Add Tab 3" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo 3" }); - desktop.toolWindow.showTabForUrl("Foo 3", true); - } - } - - Button { - text: "Destroy Tab" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.removeTabForUrl("Foo"); - } - } - - Button { - text: "Open File" - property var builder: Component { - FileDialog { } - } - - onClicked: { - var fileDialog = builder.createObject(desktop); - fileDialog.canceled.connect(function(){ - console.log("Cancelled") - }) - fileDialog.selectedFile.connect(function(file){ - console.log("Selected " + file) - }) - } - } - } - - /* Window { id: blue closable: true visible: true resizable: true - destroyOnInvisible: false + destroyOnHidden: false + title: "Blue" width: 100; height: 100 x: 1280 / 2; y: 720 / 2 @@ -289,14 +375,16 @@ ApplicationWindow { Component.onDestruction: console.log("Blue destroyed") } } - */ - /* + Window { id: green - alwaysOnTop: true closable: true visible: true - resizable: false + resizable: true + title: "Green" + destroyOnHidden: false + + width: 100; height: 100 x: 1280 / 2; y: 720 / 2 Settings { category: "TestWindow.Green" @@ -305,16 +393,43 @@ ApplicationWindow { property alias width: green.width property alias height: green.height } - width: 100; height: 100 - Rectangle { anchors.fill: parent; color: "green" } + + Rectangle { + anchors.fill: parent + visible: true + color: "green" + Component.onDestruction: console.log("Green destroyed") + } } +/* + Rectangle { width: 100; height: 100; x: 100; y: 100; color: "#00f" } + + Window { + id: green + alwaysOnTop: true + frame: HiddenFrame{} + hideBackground: true + closable: true + visible: true + resizable: false + x: 1280 / 2; y: 720 / 2 + width: 100; height: 100 + Rectangle { + color: "#0f0" + width: green.width; + height: green.height; + } + } + */ + +/* Window { id: yellow - objectName: "Yellow" closable: true visible: true resizable: true + x: 100; y: 100 width: 100; height: 100 Rectangle { anchors.fill: parent @@ -322,10 +437,11 @@ ApplicationWindow { color: "yellow" } } - */ +*/ } Action { + id: openBrowserAction text: "Open Browser" shortcut: "Ctrl+Shift+X" onTriggered: { @@ -336,3 +452,7 @@ ApplicationWindow { } } } + + + + diff --git a/tests/ui/qmlscratch.pro b/tests/ui/qmlscratch.pro index 417d7dad5b..151893de2f 100644 --- a/tests/ui/qmlscratch.pro +++ b/tests/ui/qmlscratch.pro @@ -18,6 +18,7 @@ DISTFILES += \ qml/*.qml \ ../../interface/resources/qml/*.qml \ ../../interface/resources/qml/controls/*.qml \ + ../../interface/resources/qml/controls-uit/*.qml \ ../../interface/resources/qml/dialogs/*.qml \ ../../interface/resources/qml/dialogs/fileDialog/*.qml \ ../../interface/resources/qml/dialogs/preferences/*.qml \ @@ -25,9 +26,10 @@ DISTFILES += \ ../../interface/resources/qml/desktop/*.qml \ ../../interface/resources/qml/menus/*.qml \ ../../interface/resources/qml/styles/*.qml \ + ../../interface/resources/qml/styles-uit/*.qml \ ../../interface/resources/qml/windows/*.qml \ ../../interface/resources/qml/hifi/*.qml \ + ../../interface/resources/qml/hifi/toolbars/*.qml \ ../../interface/resources/qml/hifi/dialogs/*.qml \ ../../interface/resources/qml/hifi/dialogs/preferences/*.qml \ ../../interface/resources/qml/hifi/overlays/*.qml - diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp index 0cabfe28f5..e3cf37ba04 100644 --- a/tests/ui/src/main.cpp +++ b/tests/ui/src/main.cpp @@ -86,9 +86,11 @@ int main(int argc, char *argv[]) { setChild(engine, "offscreenFlags"); setChild(engine, "Account"); setChild(engine, "ApplicationCompositor"); + setChild(engine, "Controller"); setChild(engine, "Desktop"); setChild(engine, "ScriptDiscoveryService"); setChild(engine, "HMD"); + setChild(engine, "GL"); setChild(engine, "MenuHelper"); setChild(engine, "Preferences"); setChild(engine, "urlHandler"); @@ -101,3 +103,4 @@ int main(int argc, char *argv[]) { } #include "main.moc" + diff --git a/tools/scribe/src/TextTemplate.cpp b/tools/scribe/src/TextTemplate.cpp index 741ddb6846..89937c4da6 100755 --- a/tools/scribe/src/TextTemplate.cpp +++ b/tools/scribe/src/TextTemplate.cpp @@ -748,6 +748,8 @@ int TextTemplate::evalBlockGeneration(std::ostream& dst, const BlockPointer& blo Vars::iterator it = vars.find(val); if (it != vars.end()) { val = (*it).second; + } else { + val = Tag::NULL_VAR; } } @@ -759,14 +761,19 @@ int TextTemplate::evalBlockGeneration(std::ostream& dst, const BlockPointer& blo if (val != Tag::NULL_VAR) { vars.insert(Vars::value_type(funcBlock->command.arguments[i], val)); } - paramCache.push_back(""); + + paramCache.push_back(Tag::NULL_VAR); } } generateTree(dst, funcBlock, vars); for (int i = 1; i < nbParams; i++) { - vars[ funcBlock->command.arguments[i] ] = paramCache[i]; + if (paramCache[i] == Tag::NULL_VAR) { + vars.erase(funcBlock->command.arguments[i]); + } else { + vars[funcBlock->command.arguments[i]] = paramCache[i]; + } } } } diff --git a/tools/scribe/src/main.cpp b/tools/scribe/src/main.cpp index b7038e392e..810f6c0f45 100755 --- a/tools/scribe/src/main.cpp +++ b/tools/scribe/src/main.cpp @@ -240,4 +240,5 @@ int main (int argc, char** argv) { } return 0; + } diff --git a/tools/vhacd-util/src/VHACDUtil.cpp b/tools/vhacd-util/src/VHACDUtil.cpp index 57ad185f45..9e28d33120 100644 --- a/tools/vhacd-util/src/VHACDUtil.cpp +++ b/tools/vhacd-util/src/VHACDUtil.cpp @@ -9,10 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include #include "VHACDUtil.h" -const float COLLISION_TETRAHEDRON_SCALE = 0.25f; +#include +#include + +#include // FBXReader jumbles the order of the meshes by reading them back out of a hashtable. This will put @@ -27,13 +29,16 @@ void reSortFBXGeometryMeshes(FBXGeometry& geometry) { // Read all the meshes from provided FBX file bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { + if (_verbose) { + qDebug() << "reading FBX file =" << filename << "..."; + } // open the fbx file QFile fbx(filename); if (!fbx.open(QIODevice::ReadOnly)) { + qWarning() << "unable to open FBX file =" << filename; return false; } - std::cout << "Reading FBX.....\n"; try { QByteArray fbxContents = fbx.readAll(); FBXGeometry* geom; @@ -42,14 +47,14 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { } else if (filename.toLower().endsWith(".fbx")) { geom = readFBX(fbxContents, QVariantHash(), filename); } else { - qDebug() << "unknown file extension"; + qWarning() << "file has unknown extension" << filename; return false; } result = *geom; reSortFBXGeometryMeshes(result); } catch (const QString& error) { - qDebug() << "Error reading " << filename << ": " << error; + qWarning() << "error reading" << filename << ":" << error; return false; } @@ -57,68 +62,62 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { } -unsigned int getTrianglesInMeshPart(const FBXMeshPart &meshPart, std::vector& triangles) { - // append all the triangles (and converted quads) from this mesh-part to triangles - std::vector meshPartTriangles = meshPart.triangleIndices.toStdVector(); - triangles.insert(triangles.end(), meshPartTriangles.begin(), meshPartTriangles.end()); - - // convert quads to triangles - unsigned int triangleCount = meshPart.triangleIndices.size() / 3; - unsigned int quadCount = meshPart.quadIndices.size() / 4; - for (unsigned int i = 0; i < quadCount; i++) { - unsigned int p0Index = meshPart.quadIndices[i * 4]; - unsigned int p1Index = meshPart.quadIndices[i * 4 + 1]; - unsigned int p2Index = meshPart.quadIndices[i * 4 + 2]; - unsigned int p3Index = meshPart.quadIndices[i * 4 + 3]; - // split each quad into two triangles - triangles.push_back(p0Index); - triangles.push_back(p1Index); - triangles.push_back(p2Index); - triangles.push_back(p0Index); - triangles.push_back(p2Index); - triangles.push_back(p3Index); - triangleCount += 2; +void getTrianglesInMeshPart(const FBXMeshPart &meshPart, std::vector& triangleIndices) { + // append triangle indices + triangleIndices.reserve(triangleIndices.size() + (size_t)meshPart.triangleIndices.size()); + for (auto index : meshPart.triangleIndices) { + triangleIndices.push_back(index); } - return triangleCount; + // convert quads to triangles + const uint32_t QUAD_STRIDE = 4; + uint32_t numIndices = (uint32_t)meshPart.quadIndices.size(); + for (uint32_t i = 0; i < numIndices; i += QUAD_STRIDE) { + uint32_t p0Index = meshPart.quadIndices[i]; + uint32_t p1Index = meshPart.quadIndices[i + 1]; + uint32_t p2Index = meshPart.quadIndices[i + 2]; + uint32_t p3Index = meshPart.quadIndices[i + 3]; + // split each quad into two triangles + triangleIndices.push_back(p0Index); + triangleIndices.push_back(p1Index); + triangleIndices.push_back(p2Index); + triangleIndices.push_back(p0Index); + triangleIndices.push_back(p2Index); + triangleIndices.push_back(p3Index); + } } - -void vhacd::VHACDUtil::fattenMeshes(const FBXMesh& mesh, FBXMesh& result, - unsigned int& meshPartCount, - unsigned int startMeshIndex, unsigned int endMeshIndex) const { +void vhacd::VHACDUtil::fattenMesh(const FBXMesh& mesh, const glm::mat4& geometryOffset, FBXMesh& result) const { // this is used to make meshes generated from a highfield collidable. each triangle // is converted into a tetrahedron and made into its own mesh-part. - std::vector triangles; + std::vector triangleIndices; foreach (const FBXMeshPart &meshPart, mesh.parts) { - if (meshPartCount < startMeshIndex || meshPartCount >= endMeshIndex) { - meshPartCount++; - continue; - } - getTrianglesInMeshPart(meshPart, triangles); + getTrianglesInMeshPart(meshPart, triangleIndices); } - auto triangleCount = triangles.size() / 3; - if (triangleCount == 0) { + if (triangleIndices.size() == 0) { return; } int indexStartOffset = result.vertices.size(); // new mesh gets the transformed points from the original + glm::mat4 totalTransform = geometryOffset * mesh.modelTransform; for (int i = 0; i < mesh.vertices.size(); i++) { // apply the source mesh's transform to the points - glm::vec4 v = mesh.modelTransform * glm::vec4(mesh.vertices[i], 1.0f); + glm::vec4 v = totalTransform * glm::vec4(mesh.vertices[i], 1.0f); result.vertices += glm::vec3(v); } // turn each triangle into a tetrahedron - for (unsigned int i = 0; i < triangleCount; i++) { - int index0 = triangles[i * 3] + indexStartOffset; - int index1 = triangles[i * 3 + 1] + indexStartOffset; - int index2 = triangles[i * 3 + 2] + indexStartOffset; + const uint32_t TRIANGLE_STRIDE = 3; + const float COLLISION_TETRAHEDRON_SCALE = 0.25f; + for (uint32_t i = 0; i < triangleIndices.size(); i += TRIANGLE_STRIDE) { + int index0 = triangleIndices[i] + indexStartOffset; + int index1 = triangleIndices[i + 1] + indexStartOffset; + int index2 = triangleIndices[i + 2] + indexStartOffset; // TODO: skip triangles with a normal that points more negative-y than positive-y @@ -155,156 +154,304 @@ void vhacd::VHACDUtil::fattenMeshes(const FBXMesh& mesh, FBXMesh& result, } } - - AABox getAABoxForMeshPart(const FBXMesh& mesh, const FBXMeshPart &meshPart) { AABox aaBox; - unsigned int triangleCount = meshPart.triangleIndices.size() / 3; - for (unsigned int i = 0; i < triangleCount; ++i) { - aaBox += mesh.vertices[meshPart.triangleIndices[i * 3]]; - aaBox += mesh.vertices[meshPart.triangleIndices[i * 3 + 1]]; - aaBox += mesh.vertices[meshPart.triangleIndices[i * 3 + 2]]; + const int TRIANGLE_STRIDE = 3; + for (int i = 0; i < meshPart.triangleIndices.size(); i += TRIANGLE_STRIDE) { + aaBox += mesh.vertices[meshPart.triangleIndices[i]]; + aaBox += mesh.vertices[meshPart.triangleIndices[i + 1]]; + aaBox += mesh.vertices[meshPart.triangleIndices[i + 2]]; } - unsigned int quadCount = meshPart.quadIndices.size() / 4; - for (unsigned int i = 0; i < quadCount; ++i) { - aaBox += mesh.vertices[meshPart.quadIndices[i * 4]]; - aaBox += mesh.vertices[meshPart.quadIndices[i * 4 + 1]]; - aaBox += mesh.vertices[meshPart.quadIndices[i * 4 + 2]]; - aaBox += mesh.vertices[meshPart.quadIndices[i * 4 + 3]]; + const int QUAD_STRIDE = 4; + for (int i = 0; i < meshPart.quadIndices.size(); i += QUAD_STRIDE) { + aaBox += mesh.vertices[meshPart.quadIndices[i]]; + aaBox += mesh.vertices[meshPart.quadIndices[i + 1]]; + aaBox += mesh.vertices[meshPart.quadIndices[i + 2]]; + aaBox += mesh.vertices[meshPart.quadIndices[i + 3]]; } return aaBox; } +class TriangleEdge { +public: + TriangleEdge() {} + TriangleEdge(uint32_t A, uint32_t B) { + setIndices(A, B); + } + void setIndices(uint32_t A, uint32_t B) { + if (A < B) { + _indexA = A; + _indexB = B; + } else { + _indexA = B; + _indexB = A; + } + } + bool operator==(const TriangleEdge& other) const { + return _indexA == other._indexA && _indexB == other._indexB; + } + uint32_t getIndexA() const { return _indexA; } + uint32_t getIndexB() const { return _indexB; } +private: + uint32_t _indexA { (uint32_t)(-1) }; + uint32_t _indexB { (uint32_t)(-1) }; +}; + +namespace std { + template <> + struct hash { + std::size_t operator()(const TriangleEdge& edge) const { + // use Cantor's pairing function to generate a hash of ZxZ --> Z + uint32_t ab = edge.getIndexA() + edge.getIndexB(); + return hash()((ab * (ab + 1)) / 2 + edge.getIndexB()); + } + }; +} + +// returns false if any edge has only one adjacent triangle +bool isClosedManifold(const std::vector& triangleIndices) { + using EdgeList = std::unordered_map; + EdgeList edges; + + // count the triangles for each edge + const uint32_t TRIANGLE_STRIDE = 3; + for (uint32_t i = 0; i < triangleIndices.size(); i += TRIANGLE_STRIDE) { + TriangleEdge edge; + // the triangles indices are stored in sequential order + for (uint32_t j = 0; j < 3; ++j) { + edge.setIndices(triangleIndices[i + j], triangleIndices[i + ((j + 1) % 3)]); + + EdgeList::iterator edgeEntry = edges.find(edge); + if (edgeEntry == edges.end()) { + edges.insert(std::pair(edge, 1)); + } else { + edgeEntry->second += 1; + } + } + } + // scan for outside edge + for (auto& edgeEntry : edges) { + if (edgeEntry.second == 1) { + return false; + } + } + return true; +} + +void vhacd::VHACDUtil::getConvexResults(VHACD::IVHACD* convexifier, FBXMesh& resultMesh) const { + // Number of hulls for this input meshPart + uint32_t numHulls = convexifier->GetNConvexHulls(); + if (_verbose) { + qDebug() << " hulls =" << numHulls; + } + + // create an output meshPart for each convex hull + const uint32_t TRIANGLE_STRIDE = 3; + const uint32_t POINT_STRIDE = 3; + for (uint32_t j = 0; j < numHulls; j++) { + VHACD::IVHACD::ConvexHull hull; + convexifier->GetConvexHull(j, hull); + + resultMesh.parts.append(FBXMeshPart()); + FBXMeshPart& resultMeshPart = resultMesh.parts.last(); + + int hullIndexStart = resultMesh.vertices.size(); + resultMesh.vertices.reserve(hullIndexStart + hull.m_nPoints); + uint32_t numIndices = hull.m_nPoints * POINT_STRIDE; + for (uint32_t i = 0; i < numIndices; i += POINT_STRIDE) { + float x = hull.m_points[i]; + float y = hull.m_points[i + 1]; + float z = hull.m_points[i + 2]; + resultMesh.vertices.append(glm::vec3(x, y, z)); + } + + numIndices = hull.m_nTriangles * TRIANGLE_STRIDE; + resultMeshPart.triangleIndices.reserve(resultMeshPart.triangleIndices.size() + numIndices); + for (uint32_t i = 0; i < numIndices; i += TRIANGLE_STRIDE) { + resultMeshPart.triangleIndices.append(hull.m_triangles[i] + hullIndexStart); + resultMeshPart.triangleIndices.append(hull.m_triangles[i + 1] + hullIndexStart); + resultMeshPart.triangleIndices.append(hull.m_triangles[i + 2] + hullIndexStart); + } + if (_verbose) { + qDebug() << " hull" << j << " vertices =" << hull.m_nPoints + << " triangles =" << hull.m_nTriangles + << " FBXMeshVertices =" << resultMesh.vertices.size(); + } + } +} + +float computeDt(uint64_t start) { + return (float)(usecTimestampNow() - start) / (float)USECS_PER_SECOND; +} bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, VHACD::IVHACD::Parameters params, FBXGeometry& result, - int startMeshIndex, - int endMeshIndex, float minimumMeshSize, float maximumMeshSize) { + if (_verbose) { + qDebug() << "meshes =" << geometry.meshes.size(); + } + // count the mesh-parts - int meshCount = 0; + int numParts = 0; foreach (const FBXMesh& mesh, geometry.meshes) { - meshCount += mesh.parts.size(); + numParts += mesh.parts.size(); + } + if (_verbose) { + qDebug() << "total parts =" << numParts; } - VHACD::IVHACD * interfaceVHACD = VHACD::CreateVHACD(); - - if (startMeshIndex < 0) { - startMeshIndex = 0; - } - if (endMeshIndex < 0) { - endMeshIndex = meshCount; - } - - std::cout << "Performing V-HACD computation on " << endMeshIndex - startMeshIndex << " meshes ..... " << std::endl; + VHACD::IVHACD * convexifier = VHACD::CreateVHACD(); result.meshExtents.reset(); result.meshes.append(FBXMesh()); FBXMesh &resultMesh = result.meshes.last(); - int count = 0; + const uint32_t POINT_STRIDE = 3; + const uint32_t TRIANGLE_STRIDE = 3; + + int meshIndex = 0; + int validPartsFound = 0; foreach (const FBXMesh& mesh, geometry.meshes) { + // find duplicate points + int numDupes = 0; + std::vector dupeIndexMap; + dupeIndexMap.reserve(mesh.vertices.size()); + for (int i = 0; i < mesh.vertices.size(); ++i) { + dupeIndexMap.push_back(i); + for (int j = 0; j < i; ++j) { + float distance = glm::distance2(mesh.vertices[i], mesh.vertices[j]); + const float MAX_DUPE_DISTANCE_SQUARED = 0.000001f; + if (distance < MAX_DUPE_DISTANCE_SQUARED) { + dupeIndexMap[i] = j; + ++numDupes; + break; + } + } + } + // each mesh has its own transform to move it to model-space std::vector vertices; + glm::mat4 totalTransform = geometry.offset * mesh.modelTransform; foreach (glm::vec3 vertex, mesh.vertices) { - vertices.push_back(glm::vec3(mesh.modelTransform * glm::vec4(vertex, 1.0f))); + vertices.push_back(glm::vec3(totalTransform * glm::vec4(vertex, 1.0f))); } + uint32_t numVertices = (uint32_t)vertices.size(); + if (_verbose) { + qDebug() << "mesh" << meshIndex << ": " + << " parts =" << mesh.parts.size() << " clusters =" << mesh.clusters.size() + << " vertices =" << numVertices; + } + ++meshIndex; + + std::vector openParts; + + int partIndex = 0; + std::vector triangleIndices; foreach (const FBXMeshPart &meshPart, mesh.parts) { - - if (count < startMeshIndex || count >= endMeshIndex) { - count ++; - continue; - } - - qDebug() << "--------------------"; - - std::vector triangles; - unsigned int triangleCount = getTrianglesInMeshPart(meshPart, triangles); + triangleIndices.clear(); + getTrianglesInMeshPart(meshPart, triangleIndices); // only process meshes with triangles - if (triangles.size() <= 0) { - qDebug() << " Skipping (no triangles)..."; - count++; + if (triangleIndices.size() <= 0) { + if (_verbose) { + qDebug() << " skip part" << partIndex << "(zero triangles)"; + } + ++partIndex; continue; } - auto nPoints = vertices.size(); + // collapse dupe indices + for (auto& index : triangleIndices) { + index = dupeIndexMap[index]; + } + AABox aaBox = getAABoxForMeshPart(mesh, meshPart); const float largestDimension = aaBox.getLargestDimension(); - qDebug() << "Mesh " << count << " -- " << nPoints << " points, " << triangleCount << " triangles, " - << "size =" << largestDimension; - if (largestDimension < minimumMeshSize) { - qDebug() << " Skipping (too small)..."; - count++; + if (_verbose) { + qDebug() << " skip part" << partIndex << ": dimension =" << largestDimension << "(too small)"; + } + ++partIndex; continue; } if (maximumMeshSize > 0.0f && largestDimension > maximumMeshSize) { - qDebug() << " Skipping (too large)..."; - count++; + if (_verbose) { + qDebug() << " skip part" << partIndex << ": dimension =" << largestDimension << "(too large)"; + } + ++partIndex; continue; } + // figure out if the mesh is a closed manifold or not + bool closed = isClosedManifold(triangleIndices); + if (closed) { + uint32_t triangleCount = (uint32_t)(triangleIndices.size()) / TRIANGLE_STRIDE; + if (_verbose) { + qDebug() << " process closed part" << partIndex << ": " << " triangles =" << triangleCount; + } + + // compute approximate convex decomposition + bool success = convexifier->Compute(&vertices[0].x, POINT_STRIDE, numVertices, + &triangleIndices[0], TRIANGLE_STRIDE, triangleCount, params); + if (success) { + getConvexResults(convexifier, resultMesh); + } else if (_verbose) { + qDebug() << " failed to convexify"; + } + } else { + if (_verbose) { + qDebug() << " postpone open part" << partIndex; + } + openParts.push_back(partIndex); + } + ++partIndex; + ++validPartsFound; + } + if (! openParts.empty()) { + // combine open meshes in an attempt to produce a closed mesh + + triangleIndices.clear(); + for (auto index : openParts) { + const FBXMeshPart &meshPart = mesh.parts[index]; + getTrianglesInMeshPart(meshPart, triangleIndices); + } + + // collapse dupe indices + for (auto& index : triangleIndices) { + index = dupeIndexMap[index]; + } + + // this time we don't care if the parts are closed or not + uint32_t triangleCount = (uint32_t)(triangleIndices.size()) / TRIANGLE_STRIDE; + if (_verbose) { + qDebug() << " process remaining open parts =" << openParts.size() << ": " + << " triangles =" << triangleCount; + } // compute approximate convex decomposition - bool res = interfaceVHACD->Compute(&vertices[0].x, 3, (uint)nPoints, &triangles[0], 3, triangleCount, params); - if (!res){ - qDebug() << "V-HACD computation failed for Mesh : " << count; - count++; - continue; + bool success = convexifier->Compute(&vertices[0].x, POINT_STRIDE, numVertices, + &triangleIndices[0], TRIANGLE_STRIDE, triangleCount, params); + if (success) { + getConvexResults(convexifier, resultMesh); + } else if (_verbose) { + qDebug() << " failed to convexify"; } - - // Number of hulls for this input meshPart - unsigned int nConvexHulls = interfaceVHACD->GetNConvexHulls(); - - // create an output meshPart for each convex hull - for (unsigned int j = 0; j < nConvexHulls; j++) { - VHACD::IVHACD::ConvexHull hull; - interfaceVHACD->GetConvexHull(j, hull); - - resultMesh.parts.append(FBXMeshPart()); - FBXMeshPart &resultMeshPart = resultMesh.parts.last(); - - int hullIndexStart = resultMesh.vertices.size(); - for (unsigned int i = 0; i < hull.m_nPoints; i++) { - float x = hull.m_points[i * 3]; - float y = hull.m_points[i * 3 + 1]; - float z = hull.m_points[i * 3 + 2]; - resultMesh.vertices.append(glm::vec3(x, y, z)); - } - - for (unsigned int i = 0; i < hull.m_nTriangles; i++) { - int index0 = hull.m_triangles[i * 3] + hullIndexStart; - int index1 = hull.m_triangles[i * 3 + 1] + hullIndexStart; - int index2 = hull.m_triangles[i * 3 + 2] + hullIndexStart; - resultMeshPart.triangleIndices.append(index0); - resultMeshPart.triangleIndices.append(index1); - resultMeshPart.triangleIndices.append(index2); - } - } - - count++; } } //release memory - interfaceVHACD->Clean(); - interfaceVHACD->Release(); + convexifier->Clean(); + convexifier->Release(); - if (count > 0){ - return true; - } - else{ - return false; - } + return validPartsFound > 0; } vhacd::VHACDUtil:: ~VHACDUtil(){ @@ -319,16 +466,9 @@ void vhacd::ProgressCallback::Update(const double overallProgress, const char* const operation) { int progress = (int)(overallProgress + 0.5); - if (progress < 10){ - std::cout << "\b\b"; - } - else{ - std::cout << "\b\b\b"; - } - - std::cout << progress << "%"; - - if (progress >= 100){ + std::cout << "\b\b\b"; + std::cout << progress << "%" << std::flush; + if (progress >= 100) { std::cout << std::endl; } } diff --git a/tools/vhacd-util/src/VHACDUtil.h b/tools/vhacd-util/src/VHACDUtil.h index 34c2c22e0e..8f82c4e4e4 100644 --- a/tools/vhacd-util/src/VHACDUtil.h +++ b/tools/vhacd-util/src/VHACDUtil.h @@ -25,18 +25,23 @@ namespace vhacd { class VHACDUtil { public: + void setVerbose(bool verbose) { _verbose = verbose; } + bool loadFBX(const QString filename, FBXGeometry& result); - void fattenMeshes(const FBXMesh& mesh, FBXMesh& result, - unsigned int& meshPartCount, - unsigned int startMeshIndex, unsigned int endMeshIndex) const; + void fattenMesh(const FBXMesh& mesh, const glm::mat4& gometryOffset, FBXMesh& result) const; bool computeVHACD(FBXGeometry& geometry, VHACD::IVHACD::Parameters params, FBXGeometry& result, - int startMeshIndex, int endMeshIndex, float minimumMeshSize, float maximumMeshSize); + + void getConvexResults(VHACD::IVHACD* convexifier, FBXMesh& resultMesh) const; + ~VHACDUtil(); + + private: + bool _verbose { false }; }; class ProgressCallback : public VHACD::IVHACD::IUserCallback { @@ -45,7 +50,7 @@ namespace vhacd { ~ProgressCallback(); // Couldn't follow coding guideline here due to virtual function declared in IUserCallback - void Update(const double overallProgress, const double stageProgress, const double operationProgress, + void Update(const double overallProgress, const double stageProgress, const double operationProgress, const char * const stage, const char * const operation); }; } diff --git a/tools/vhacd-util/src/VHACDUtilApp.cpp b/tools/vhacd-util/src/VHACDUtilApp.cpp index 2af6bca337..cae184a49c 100644 --- a/tools/vhacd-util/src/VHACDUtilApp.cpp +++ b/tools/vhacd-util/src/VHACDUtilApp.cpp @@ -19,7 +19,6 @@ using namespace std; using namespace VHACD; - QString formatFloat(double n) { // limit precision to 6, but don't output trailing zeros. QString s = QString::number(n, 'f', 6); @@ -33,14 +32,15 @@ QString formatFloat(double n) { } -bool writeOBJ(QString outFileName, FBXGeometry& geometry, bool outputCentimeters, int whichMeshPart = -1) { +bool VHACDUtilApp::writeOBJ(QString outFileName, FBXGeometry& geometry, bool outputCentimeters, int whichMeshPart) { QFile file(outFileName); if (!file.open(QIODevice::WriteOnly)) { - qDebug() << "Unable to write to " << outFileName; + qWarning() << "unable to write to" << outFileName; + _returnCode = VHACD_RETURN_CODE_FAILURE_TO_WRITE; return false; } - QTextStream out(&file); + QTextStream out(&file); if (outputCentimeters) { out << "# This file uses centimeters as units\n\n"; } @@ -105,6 +105,9 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : const QCommandLineOption helpOption = parser.addHelpOption(); + const QCommandLineOption verboseOutput("v", "verbose output"); + parser.addOption(verboseOutput); + const QCommandLineOption splitOption("split", "split input-file into one mesh per output-file"); parser.addOption(splitOption); @@ -123,12 +126,6 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : const QCommandLineOption outputCentimetersOption("c", "output units are centimeters"); parser.addOption(outputCentimetersOption); - const QCommandLineOption startMeshIndexOption("s", "start-mesh index", "0"); - parser.addOption(startMeshIndexOption); - - const QCommandLineOption endMeshIndexOption("e", "end-mesh index", "0"); - parser.addOption(endMeshIndexOption); - const QCommandLineOption minimumMeshSizeOption("m", "minimum mesh (diagonal) size to consider", "0"); parser.addOption(minimumMeshSizeOption); @@ -195,8 +192,10 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : Q_UNREACHABLE(); } - bool outputCentimeters = parser.isSet(outputCentimetersOption); + bool verbose = parser.isSet(verboseOutput); + vUtil.setVerbose(verbose); + bool outputCentimeters = parser.isSet(outputCentimetersOption); bool fattenFaces = parser.isSet(fattenFacesOption); bool generateHulls = parser.isSet(generateHullsOption); bool splitModel = parser.isSet(splitOption); @@ -225,16 +224,6 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : Q_UNREACHABLE(); } - int startMeshIndex = -1; - if (parser.isSet(startMeshIndexOption)) { - startMeshIndex = parser.value(startMeshIndexOption).toInt(); - } - - int endMeshIndex = -1; - if (parser.isSet(endMeshIndexOption)) { - endMeshIndex = parser.value(endMeshIndexOption).toInt(); - } - float minimumMeshSize = 0.0f; if (parser.isSet(minimumMeshSizeOption)) { minimumMeshSize = parser.value(minimumMeshSizeOption).toFloat(); @@ -301,17 +290,20 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : Q_UNREACHABLE(); } - - // load the mesh - + // load the mesh FBXGeometry fbx; auto begin = std::chrono::high_resolution_clock::now(); if (!vUtil.loadFBX(inputFilename, fbx)){ - cout << "Error in opening FBX file...."; + _returnCode = VHACD_RETURN_CODE_FAILURE_TO_READ; + return; } auto end = std::chrono::high_resolution_clock::now(); - auto loadDuration = std::chrono::duration_cast(end - begin).count(); + if (verbose) { + auto loadDuration = std::chrono::duration_cast(end - begin).count(); + const double NANOSECS_PER_SECOND = 1.0e9; + qDebug() << "load time =" << (double)loadDuration / NANOSECS_PER_SECOND << "seconds"; + } if (splitModel) { QVector infileExtensions = {"fbx", "obj"}; @@ -329,10 +321,14 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : if (generateHulls) { VHACD::IVHACD::Parameters params; - vhacd::ProgressCallback pCallBack; + vhacd::ProgressCallback progressCallback; //set parameters for V-HACD - params.m_callback = &pCallBack; //progress callback + if (verbose) { + params.m_callback = &progressCallback; //progress callback + } else { + params.m_callback = nullptr; + } params.m_resolution = vHacdResolution; params.m_depth = vHacdDepth; params.m_concavity = vHacdConcavity; @@ -346,44 +342,51 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : params.m_mode = 0; // 0: voxel-based (recommended), 1: tetrahedron-based params.m_maxNumVerticesPerCH = vHacdMaxVerticesPerCH; params.m_minVolumePerCH = 0.0001; // 0.0001 - params.m_callback = 0; // 0 - params.m_logger = 0; // 0 + params.m_logger = nullptr; params.m_convexhullApproximation = true; // true params.m_oclAcceleration = true; // true //perform vhacd computation + if (verbose) { + qDebug() << "running V-HACD algorithm ..."; + } begin = std::chrono::high_resolution_clock::now(); FBXGeometry result; - if (!vUtil.computeVHACD(fbx, params, result, startMeshIndex, endMeshIndex, - minimumMeshSize, maximumMeshSize)) { - cout << "Compute Failed..."; - } + bool success = vUtil.computeVHACD(fbx, params, result, minimumMeshSize, maximumMeshSize); + end = std::chrono::high_resolution_clock::now(); auto computeDuration = std::chrono::duration_cast(end - begin).count(); + if (verbose) { + qDebug() << "run time =" << (double)computeDuration / 1000000000.00 << " seconds"; + } + + if (!success) { + if (verbose) { + qDebug() << "failed to convexify model"; + } + _returnCode = VHACD_RETURN_CODE_FAILURE_TO_CONVEXIFY; + return; + } int totalVertices = 0; int totalTriangles = 0; - int totalMeshParts = 0; foreach (const FBXMesh& mesh, result.meshes) { totalVertices += mesh.vertices.size(); foreach (const FBXMeshPart &meshPart, mesh.parts) { totalTriangles += meshPart.triangleIndices.size() / 3; // each quad was made into two triangles totalTriangles += 2 * meshPart.quadIndices.size() / 4; - totalMeshParts++; } } - int totalHulls = result.meshes[0].parts.size(); - cout << endl << "Summary of V-HACD Computation..................." << endl; - cout << "File Path : " << inputFilename.toStdString() << endl; - cout << "Number Of Meshes : " << totalMeshParts << endl; - cout << "Total vertices : " << totalVertices << endl; - cout << "Total Triangles : " << totalTriangles << endl; - cout << "Total Convex Hulls : " << totalHulls << endl; - cout << "Total FBX load time: " << (double)loadDuration / 1000000000.00 << " seconds" << endl; - cout << "V-HACD Compute time: " << (double)computeDuration / 1000000000.00 << " seconds" << endl; + if (verbose) { + int totalHulls = result.meshes[0].parts.size(); + qDebug() << "output file =" << outputFilename; + qDebug() << "vertices =" << totalVertices; + qDebug() << "triangles =" << totalTriangles; + qDebug() << "hulls =" << totalHulls; + } writeOBJ(outputFilename, result, outputCentimeters); } @@ -398,17 +401,9 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : meshCount += mesh.parts.size(); } - if (startMeshIndex < 0) { - startMeshIndex = 0; - } - if (endMeshIndex < 0) { - endMeshIndex = meshCount; - } - - unsigned int meshPartCount = 0; result.modelTransform = glm::mat4(); // Identity matrix foreach (const FBXMesh& mesh, fbx.meshes) { - vUtil.fattenMeshes(mesh, result, meshPartCount, startMeshIndex, endMeshIndex); + vUtil.fattenMesh(mesh, fbx.offset, result); } newFbx.meshes.append(result); diff --git a/tools/vhacd-util/src/VHACDUtilApp.h b/tools/vhacd-util/src/VHACDUtilApp.h index 016b7b7b2f..ebb8164634 100644 --- a/tools/vhacd-util/src/VHACDUtilApp.h +++ b/tools/vhacd-util/src/VHACDUtilApp.h @@ -15,12 +15,25 @@ #include +#include + +const int VHACD_RETURN_CODE_FAILURE_TO_READ = 1; +const int VHACD_RETURN_CODE_FAILURE_TO_WRITE = 2; +const int VHACD_RETURN_CODE_FAILURE_TO_CONVEXIFY = 3; + class VHACDUtilApp : public QCoreApplication { Q_OBJECT - public: +public: VHACDUtilApp(int argc, char* argv[]); ~VHACDUtilApp(); + + bool writeOBJ(QString outFileName, FBXGeometry& geometry, bool outputCentimeters, int whichMeshPart = -1); + + int getReturnCode() const { return _returnCode; } + +private: + int _returnCode { 0 }; }; diff --git a/tools/vhacd-util/src/main.cpp b/tools/vhacd-util/src/main.cpp index 0e8d72abd3..42c9db9513 100644 --- a/tools/vhacd-util/src/main.cpp +++ b/tools/vhacd-util/src/main.cpp @@ -23,5 +23,5 @@ using namespace VHACD; int main(int argc, char * argv[]) { VHACDUtilApp app(argc, argv); - return 0; + return app.getReturnCode(); } diff --git a/unpublishedScripts/DomainContent/Home/blocky/arrangement6B.json b/unpublishedScripts/DomainContent/Home/blocky/arrangement6B.json new file mode 100644 index 0000000000..2a0510e259 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/blocky/arrangement6B.json @@ -0,0 +1,231 @@ +{ + "Entities": [ + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "created": "2016-05-09T19:37:13Z", + "dimensions": { + "x": 0.029999999329447746, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{5692ed0d-52ae-4bd9-8ef6-13a3d28dbcec}", + "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", + "name": "home_model_blocky_block", + "parentID": "{8c29606d-b071-4793-a5d5-26453bb12bf4}", + "position": { + "x": 0.076191246509552002, + "y": -0.0050119552761316299, + "z": -4.275888204574585e-05 + }, + "queryAACube": { + "scale": 0.77012991905212402, + "x": 1104.9146728515625, + "y": 460.09011840820312, + "z": -69.708526611328125 + }, + "rotation": { + "w": -3.1195580959320068e-05, + "x": -0.99968332052230835, + "y": 0.019217833876609802, + "z": 0.0013828873634338379 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "created": "2016-05-09T19:28:29Z", + "dimensions": { + "x": 0.029999999329447746, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{41fdce7a-5c15-4335-a7d4-e6e42c419edb}", + "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", + "name": "home_model_block", + "parentID": "{8c29606d-b071-4793-a5d5-26453bb12bf4}", + "position": { + "x": 0.31622248888015747, + "y": -0.076068185269832611, + "z": -0.00023670494556427002 + }, + "queryAACube": { + "scale": 0.77012991905212402, + "x": 1104.552734375, + "y": 460.09014892578125, + "z": -69.711776733398438 + }, + "rotation": { + "w": 0.9812014102935791, + "x": 2.8990209102630615e-05, + "y": 0.00053216516971588135, + "z": -0.19223560392856598 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "created": "2016-05-09T19:30:40Z", + "dimensions": { + "x": 0.029999999329447746, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{376ea748-6b7b-4ac4-9194-b0f118ac39dd}", + "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", + "name": "home_model_blocky_block", + "parentID": "{8c29606d-b071-4793-a5d5-26453bb12bf4}", + "position": { + "x": -0.22965218126773834, + "y": 0.0041047781705856323, + "z": 0.00015974044799804688 + }, + "queryAACube": { + "scale": 0.77012991905212402, + "x": 1105.10009765625, + "y": 460.09011840820312, + "z": -69.742721557617188 + }, + "rotation": { + "w": -7.3388218879699707e-06, + "x": -0.99162417650222778, + "y": 0.1281534731388092, + "z": 0.00039628148078918457 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "created": "2016-05-09T21:28:38Z", + "dimensions": { + "x": 0.029999999329447746, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{d1359425-9129-4827-b881-e4802703a0ca}", + "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", + "name": "home_model_blocky_block", + "parentID": "{8c29606d-b071-4793-a5d5-26453bb12bf4}", + "position": { + "x": 0.23440317809581757, + "y": -0.035929545760154724, + "z": -0.00016139447689056396 + }, + "queryAACube": { + "scale": 0.77012991905212402, + "x": 1104.6361083984375, + "y": 460.09011840820312, + "z": -69.792564392089844 + }, + "rotation": { + "w": 0.986641526222229, + "x": -0.00016996264457702637, + "y": -0.00021627545356750488, + "z": -0.16198150813579559 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "created": "2016-05-09T19:32:51Z", + "dimensions": { + "x": 0.029999999329447746, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{11b3c61c-abe3-48ac-9033-3f88d3efee00}", + "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", + "name": "home_model_blocky_block", + "parentID": "{8c29606d-b071-4793-a5d5-26453bb12bf4}", + "position": { + "x": -0.15134727954864502, + "y": 0.00060278922319412231, + "z": 0.00011575222015380859 + }, + "queryAACube": { + "scale": 0.77012991905212402, + "x": 1104.79541015625, + "y": 460.090087890625, + "z": -69.902366638183594 + }, + "rotation": { + "w": -0.00098252296447753906, + "x": -0.030791401863098145, + "y": 0.99939167499542236, + "z": 0.0002511441707611084 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "created": "2016-05-09T19:32:51Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{15c3aeae-1166-4cff-aa53-d732f20a1203}", + "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", + "name": "home_model_blocky_block", + "parentID": "{8c29606d-b071-4793-a5d5-26453bb12bf4}", + "position": { + "x": -0.35397925972938538, + "y": 0.020401254296302795, + "z": 0.00025978684425354004 + }, + "queryAACube": { + "scale": 0.82158386707305908, + "x": 1105.195068359375, + "y": 460.06436157226562, + "z": -69.863059997558594 + }, + "rotation": { + "w": 0.68755638599395752, + "x": -0.00055142492055892944, + "y": 0.00033529102802276611, + "z": -0.72594141960144043 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "angularDamping": 0, + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "created": "2016-05-09T19:30:40Z", + "dimensions": { + "x": 0.029999999329447746, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{8c29606d-b071-4793-a5d5-26453bb12bf4}", + "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", + "name": "home_model_blocky_block", + "queryAACube": { + "scale": 0.25670996308326721, + "x": -0.12835498154163361, + "y": -0.12835498154163361, + "z": -0.12835498154163361 + }, + "rotation": { + "w": 0.097722500562667847, + "x": 0.097280360758304596, + "y": 0.70029336214065552, + "z": -0.70041131973266602 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + } + ], + "Version": 57 +} diff --git a/unpublishedScripts/DomainContent/Home/blocky/blocky.js b/unpublishedScripts/DomainContent/Home/blocky/blocky.js new file mode 100644 index 0000000000..8eae31a439 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/blocky/blocky.js @@ -0,0 +1,286 @@ +// this script creates a pile of playable blocks and displays a target arrangement when you trigger it + +var BLOCK_RED = { + url: 'atp:/kineticObjects/blocks/planky_red.fbx', + dimensions: { + "x": 0.10000000149011612, + "y": 0.05000000074505806, + "z": 0.25 + } +}; +var BLOCK_BLUE = { + url: 'atp:/kineticObjects/blocks/planky_blue.fbx', + dimensions: { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.25 + }, +}; +var BLOCK_YELLOW = { + url: 'atp:/kineticObjects/blocks/planky_yellow.fbx', + dimensions: { + "x": 0.029999999329447746, + "y": 0.05000000074505806, + "z": 0.25 + } +}; +var BLOCK_GREEN = { + url: 'atp:/kineticObjects/blocks/planky_green.fbx', + dimensions: { + "x": 0.10000000149011612, + "y": 0.10000000149011612, + "z": 0.25 + }, +}; +var BLOCK_NATURAL = { + url: "atp:/kineticObjects/blocks/planky_natural.fbx", + dimensions: { + "x": 0.05, + "y": 0.05, + "z": 0.05 + } +}; + +var blocks = [ + BLOCK_RED, BLOCK_BLUE, BLOCK_YELLOW, BLOCK_GREEN, BLOCK_NATURAL +]; + +var arrangements = [{ + name: 'greenhenge', + blocks: [BLOCK_GREEN, BLOCK_GREEN, BLOCK_YELLOW, BLOCK_YELLOW], + target: "atp:/blocky/newblocks1.json" +}, { + name: 'tallstuff', + blocks: [BLOCK_RED, BLOCK_RED, BLOCK_RED], + target: "atp:/blocky/newblocks2.json" +}, { + name: 'ostrich', + blocks: [BLOCK_RED, BLOCK_RED, BLOCK_GREEN, BLOCK_YELLOW, BLOCK_NATURAL], + target: "atp:/blocky/newblocks5.json" +}, { + name: 'fourppl', + blocks: [BLOCK_BLUE, BLOCK_BLUE, BLOCK_BLUE, BLOCK_BLUE, BLOCK_NATURAL, BLOCK_NATURAL, BLOCK_NATURAL, BLOCK_NATURAL], + target: "atp:/blocky/newblocks3.json" +}, { + name: 'frogguy', + blocks: [BLOCK_GREEN, BLOCK_GREEN, BLOCK_GREEN, BLOCK_YELLOW, BLOCK_RED, BLOCK_RED, BLOCK_NATURAL, BLOCK_NATURAL], + target: "atp:/blocky/newblocks4.json" +}, { + name: 'dominoes', + blocks: [BLOCK_RED, BLOCK_YELLOW, BLOCK_YELLOW, BLOCK_YELLOW, BLOCK_YELLOW, BLOCK_YELLOW, BLOCK_YELLOW], + target: "atp:/blocky/arrangement6B.json" +}] + +var PLAYABLE_BLOCKS_POSITION = { + x: 1097.6, + y: 460.5, + z: -66.22 +}; + +var TARGET_BLOCKS_POSITION = { + x: 1096.82, + y: 460.5, + z: -67.689 +}; +//#debug +(function() { + + print('BLOCK ENTITY SCRIPT') + var _this; + + function Blocky() { + _this = this; + } + + Blocky.prototype = { + busy: false, + debug: false, + playableBlocks: [], + targetBlocks: [], + preload: function(entityID) { + print('BLOCKY preload') + this.entityID = entityID; + Script.update.connect(_this.update); + }, + + createTargetBlocks: function(arrangement) { + var created = []; + print('BLOCKY create target blocks') + + var created = []; + var success = Clipboard.importEntities(arrangement.target); + if (success === true) { + created = Clipboard.pasteEntities(TARGET_BLOCKS_POSITION) + print('created ' + created); + } + + this.targetBlocks = created; + print('BLOCKY TARGET BLOCKS:: ' + this.targetBlocks); + }, + + createPlayableBlocks: function(arrangement) { + print('BLOCKY creating playable blocks' + arrangement.blocks.length); + arrangement.blocks.forEach(function(block) { + print('BLOCKY in a block loop') + var blockProps = { + name: "home_model_blocky_block", + type: 'Model', + collisionSoundURL: "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + dynamic: true, + collisionless: false, + gravity: { + x: 0, + y: -9.8, + z: 0 + }, + userData: JSON.stringify({ + grabbableKey: { + grabbable: true + }, + hifiHomeKey: { + reset: true + } + }), + dimensions: block.dimensions, + modelURL: block.url, + shapeType: 'box', + velocity: { + x: 0, + y: -0.01, + z: 0, + }, + position: PLAYABLE_BLOCKS_POSITION + } + var newBlock = Entities.addEntity(blockProps); + print('BLOCKY made a playable block' + newBlock) + _this.playableBlocks.push(newBlock); + print('BLOCKY pushing it into playable blocks'); + }) + + print('BLOCKY after going through playable arrangement') + }, + + startNearTrigger: function() { + print('BLOCKY got a near trigger'); + this.advanceRound(); + }, + + advanceRound: function() { + print('BLOCKY advance round'); + this.busy = true; + this.cleanup(); + var arrangement = arrangements[Math.floor(Math.random() * arrangements.length)]; + this.createTargetBlocks(arrangement); + + if (this.debug === true) { + this.debugCreatePlayableBlocks(); + } else { + this.createPlayableBlocks(arrangement); + + } + Script.setTimeout(function() { + _this.busy = false; + }, 1000) + }, + + findBlocks: function() { + var found = []; + var results = Entities.findEntities(MyAvatar.position, 10); + results.forEach(function(result) { + var properties = Entities.getEntityProperties(result); + print('got result props') + if (properties.name.indexOf('blocky_block') > -1) { + found.push(result); + } + }); + return found; + }, + + cleanup: function() { + print('BLOCKY cleanup'); + var blocks = this.findBlocks(); + print('BLOCKY cleanup2' + blocks.length) + blocks.forEach(function(block) { + Entities.deleteEntity(block); + }) + print('BLOCKY after find and delete') + this.targetBlocks.forEach(function(block) { + Entities.deleteEntity(block); + }); + this.playableBlocks.forEach(function(block) { + Entities.deleteEntity(block); + }); + this.targetBlocks = []; + this.playableBlocks = []; + }, + + debugCreatePlayableBlocks: function() { + print('BLOCKY debug create'); + var howMany = 10; + var i; + for (i = 0; i < howMany; i++) { + var block = blocks[Math.floor(Math.random() * blocks.length)]; + var blockProps = { + name: "home_model_blocky_block", + type: 'Model', + collisionSoundURL: "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + dynamic: false, + collisionless: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: true + }, + hifiHomeKey: { + reset: true + } + }), + dimensions: block.dimensions, + modelURL: block.url, + shapeType: 'box', + velocity: { + x: 0, + y: -0.01, + z: 0, + }, + position: PLAYABLE_BLOCKS_POSITION + } + this.playableBlocks.push(Entities.addEntity(blockProps)); + } + }, + + unload: function() { + this.cleanup(); + Script.update.disconnect(_this.update); + }, + + clickReleaseOnEntity: function() { + print('BLOCKY click') + this.startNearTrigger(); + }, + + update: function() { + if (_this.busy === true) { + return; + } + var BEAM_TRIGGER_THRESHOLD = 0.075; + + var BEAM_POSITION = { + x: 1098.4424, + y: 460.3090, + z: -66.2190 + }; + + var leftHandPosition = MyAvatar.getLeftPalmPosition(); + var rightHandPosition = MyAvatar.getRightPalmPosition(); + + var rightDistance = Vec3.distance(leftHandPosition, BEAM_POSITION) + var leftDistance = Vec3.distance(rightHandPosition, BEAM_POSITION) + + if (rightDistance < BEAM_TRIGGER_THRESHOLD || leftDistance < BEAM_TRIGGER_THRESHOLD) { + _this.startNearTrigger(); + } + } + } + + return new Blocky(); +}) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/blocky/newblocks1.json b/unpublishedScripts/DomainContent/Home/blocky/newblocks1.json new file mode 100644 index 0000000000..2409c7c34c --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/blocky/newblocks1.json @@ -0,0 +1,136 @@ +{ + "Entities": [ + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:35:13Z", + "dimensions": { + "x": 0.029999999329447746, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{aad3b1c3-6478-46dd-a985-800d0e38c8e7}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.069580078125, + "y": 0.153778076171875, + "z": 0.04181671142578125 + }, + "queryAACube": { + "scale": 0.25670996308326721, + "x": -0.058774903416633606, + "y": 0.025423094630241394, + "z": -0.086538270115852356 + }, + "rotation": { + "w": 0.95882409811019897, + "x": -1.896415778901428e-05, + "y": 0.28400072455406189, + "z": -1.0296697837475222e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:30:51Z", + "dimensions": { + "x": 0.029999999329447746, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{f5714678-d436-4353-b0c8-2d73599dda3a}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.0181884765625, + "y": 0.153778076171875, + "z": 0.0773468017578125 + }, + "queryAACube": { + "scale": 0.25670996308326721, + "x": -0.11016650497913361, + "y": 0.025423094630241394, + "z": -0.051008179783821106 + }, + "rotation": { + "w": 0.95882409811019897, + "x": -1.896415778901428e-05, + "y": 0.28400072455406189, + "z": -1.0296697837475222e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:30:51Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.10000000149011612, + "z": 0.25 + }, + "id": "{0cf93a58-18d2-4c69-9c1b-33d6d1e647b8}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_green.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.078857421875, + "y": 0.002899169921875, + "z": 0.11455535888671875 + }, + "queryAACube": { + "scale": 0.28722813725471497, + "x": -0.064756646752357483, + "y": -0.14071489870548248, + "z": -0.029058709740638733 + }, + "rotation": { + "w": 0.33741602301597595, + "x": -0.33740735054016113, + "y": 0.62139773368835449, + "z": 0.62142699956893921 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:33:02Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.10000000149011612, + "z": 0.25 + }, + "id": "{7f8791fb-e236-4280-b9d4-4ee6d1663e2a}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_green.fbx", + "name": "home_model_blocky_block", + "queryAACube": { + "scale": 0.28722813725471497, + "x": -0.14361406862735748, + "y": -0.14361406862735748, + "z": -0.14361406862735748 + }, + "rotation": { + "w": 0.68138498067855835, + "x": -0.68140000104904175, + "y": 0.18894240260124207, + "z": 0.1889689564704895 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + } + ], + "Version": 57 +} diff --git a/unpublishedScripts/DomainContent/Home/blocky/newblocks2.json b/unpublishedScripts/DomainContent/Home/blocky/newblocks2.json new file mode 100644 index 0000000000..8487e709d4 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/blocky/newblocks2.json @@ -0,0 +1,107 @@ +{ + "Entities": [ + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:37:24Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{2454f490-787c-491b-9b7e-c0e098af86e8}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0, + "y": 0.504150390625, + "z": 0 + }, + "queryAACube": { + "scale": 0.27386128902435303, + "x": -0.13693064451217651, + "y": 0.36721974611282349, + "z": -0.13693064451217651 + }, + "rotation": { + "w": 0.70711755752563477, + "x": 0.70709598064422607, + "y": 0, + "z": -2.1579186068265699e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:37:24Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{5bf66e29-a3b6-4171-8470-6e0462d51dd3}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0, + "y": 0, + "z": 0.00112152099609375 + }, + "queryAACube": { + "scale": 0.27386128902435303, + "x": -0.13693064451217651, + "y": -0.13693064451217651, + "z": -0.13580912351608276 + }, + "rotation": { + "w": 0.70711755752563477, + "x": 0.70709598064422607, + "y": 0, + "z": -2.1579186068265699e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:39:35Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{2cf52537-9f88-41eb-86d6-14a5ff218fb6}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0, + "y": 0.253204345703125, + "z": 0 + }, + "queryAACube": { + "scale": 0.27386128902435303, + "x": -0.13693064451217651, + "y": 0.11627370119094849, + "z": -0.13693064451217651 + }, + "rotation": { + "w": 0.70711755752563477, + "x": 0.70709598064422607, + "y": 0, + "z": -2.1579186068265699e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + } + ], + "Version": 57 +} diff --git a/unpublishedScripts/DomainContent/Home/blocky/newblocks3.json b/unpublishedScripts/DomainContent/Home/blocky/newblocks3.json new file mode 100644 index 0000000000..846800a2eb --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/blocky/newblocks3.json @@ -0,0 +1,277 @@ +{ + "Entities": [ + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:41:47Z", + "dimensions": { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.05000000074505806 + }, + "id": "{806e18fc-1ffd-4d7a-a405-52f1ad6cc164}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_natural.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.1412353515625, + "y": 0.150634765625, + "z": 0.19216156005859375 + }, + "queryAACube": { + "scale": 0.086602538824081421, + "x": 0.09793408215045929, + "y": 0.10733349621295929, + "z": 0.14886029064655304 + }, + "rotation": { + "w": 0.96592974662780762, + "x": -1.8688122509047389e-05, + "y": 0.25880429148674011, + "z": -1.0789593034132849e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:41:47Z", + "dimensions": { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.05000000074505806 + }, + "id": "{3c99f577-fc38-4f5b-8b7b-2dc36b742ff9}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_natural.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.078369140625, + "y": 0.150634765625, + "z": 0.10829925537109375 + }, + "queryAACube": { + "scale": 0.086602538824081421, + "x": 0.03506787121295929, + "y": 0.10733349621295929, + "z": 0.06499798595905304 + }, + "rotation": { + "w": 0.96592974662780762, + "x": -1.8688122509047389e-05, + "y": 0.25880429148674011, + "z": -1.0789593034132849e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:41:47Z", + "dimensions": { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.05000000074505806 + }, + "id": "{3aa37e21-48ff-4d41-be0b-d47a67e004cc}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_natural.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.2056884765625, + "y": 0.150634765625, + "z": 0.2870941162109375 + }, + "queryAACube": { + "scale": 0.086602538824081421, + "x": 0.16238720715045929, + "y": 0.10733349621295929, + "z": 0.24379284679889679 + }, + "rotation": { + "w": 0.96592974662780762, + "x": -1.8688122509047389e-05, + "y": 0.25880429148674011, + "z": -1.0789593034132849e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:39:35Z", + "dimensions": { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.05000000074505806 + }, + "id": "{a34966c4-0f1c-4103-8857-f998559318e7}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_natural.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0, + "y": 0.150634765625, + "z": 0 + }, + "queryAACube": { + "scale": 0.086602538824081421, + "x": -0.04330126941204071, + "y": 0.10733349621295929, + "z": -0.04330126941204071 + }, + "rotation": { + "w": 0.96592974662780762, + "x": -1.8688122509047389e-05, + "y": 0.25880429148674011, + "z": -1.0789593034132849e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:41:47Z", + "dimensions": { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{96336167-827c-48ec-b641-ee462fb2abdc}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_blue.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.2088623046875, + "y": 0, + "z": 0.28765869140625 + }, + "queryAACube": { + "scale": 0.25980761647224426, + "x": 0.078958496451377869, + "y": -0.12990380823612213, + "z": 0.15775488317012787 + }, + "rotation": { + "w": 0.68138498067855835, + "x": -0.68140000104904175, + "y": 0.18894240260124207, + "z": 0.1889689564704895 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:39:35Z", + "dimensions": { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{17b1be58-b3d6-4b27-856e-f74b8ba34309}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_blue.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.001953125, + "y": 0, + "z": 0.00205230712890625 + }, + "queryAACube": { + "scale": 0.25980761647224426, + "x": -0.12795068323612213, + "y": -0.12990380823612213, + "z": -0.12785150110721588 + }, + "rotation": { + "w": 0.68138498067855835, + "x": -0.68140000104904175, + "y": 0.18894240260124207, + "z": 0.1889689564704895 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:41:47Z", + "dimensions": { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{161eefc7-ba39-4301-913f-edb4f7d9236e}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_blue.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.0777587890625, + "y": 0, + "z": 0.10608673095703125 + }, + "queryAACube": { + "scale": 0.25980761647224426, + "x": -0.052145019173622131, + "y": -0.12990380823612213, + "z": -0.023817077279090881 + }, + "rotation": { + "w": 0.68138498067855835, + "x": -0.68140000104904175, + "y": 0.18894240260124207, + "z": 0.1889689564704895 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:41:47Z", + "dimensions": { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{ea1eebe2-fef3-4587-9f87-f7812359ec8b}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_blue.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.152099609375, + "y": 0, + "z": 0.1891937255859375 + }, + "queryAACube": { + "scale": 0.25980761647224426, + "x": 0.022195801138877869, + "y": -0.12990380823612213, + "z": 0.059289917349815369 + }, + "rotation": { + "w": 0.68138498067855835, + "x": -0.68140000104904175, + "y": 0.18894240260124207, + "z": 0.1889689564704895 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + } + ], + "Version": 57 +} diff --git a/unpublishedScripts/DomainContent/Home/blocky/newblocks4.json b/unpublishedScripts/DomainContent/Home/blocky/newblocks4.json new file mode 100644 index 0000000000..5d5a7a8d9d --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/blocky/newblocks4.json @@ -0,0 +1,277 @@ +{ + "Entities": [ + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:52:42Z", + "dimensions": { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.05000000074505806 + }, + "id": "{df9f2e81-c7c3-45b3-9459-0dc1647e3ac8}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_natural.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.1153564453125, + "y": 0.3056640625, + "z": 0.19878387451171875 + }, + "queryAACube": { + "scale": 0.086602538824081421, + "x": 0.07205517590045929, + "y": 0.2623627781867981, + "z": 0.15548260509967804 + }, + "rotation": { + "w": 0.87880980968475342, + "x": -6.1288210417842492e-06, + "y": -0.4771721363067627, + "z": -2.0690549717983231e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:48:20Z", + "dimensions": { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.05000000074505806 + }, + "id": "{00f70709-d22b-4e50-98b6-c49783a1ca2d}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_natural.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.0565185546875, + "y": 0.3056640625, + "z": 0.0875244140625 + }, + "queryAACube": { + "scale": 0.086602538824081421, + "x": 0.01321728527545929, + "y": 0.2623627781867981, + "z": 0.04422314465045929 + }, + "rotation": { + "w": 0.87880980968475342, + "x": -6.1288210417842492e-06, + "y": -0.4771721363067627, + "z": -2.0690549717983231e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:48:20Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{925922c7-c8da-4862-8695-9e823cde5f43}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.165771484375, + "y": 0.1016845703125, + "z": 0.3075103759765625 + }, + "queryAACube": { + "scale": 0.27386128902435303, + "x": 0.028840839862823486, + "y": -0.035246074199676514, + "z": 0.17057973146438599 + }, + "rotation": { + "w": 0.76051729917526245, + "x": 0.5797961950302124, + "y": 0.20168730616569519, + "z": -0.21159422397613525 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:48:20Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{9754d1bd-30f0-473c-87fa-159902f3ae54}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0, + "y": 0.1016845703125, + "z": 0 + }, + "queryAACube": { + "scale": 0.27386128902435303, + "x": -0.13693064451217651, + "y": -0.035246074199676514, + "z": -0.13693064451217651 + }, + "rotation": { + "w": -0.24484673142433167, + "x": -0.2088056355714798, + "y": 0.70473498106002808, + "z": -0.63229680061340332 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:48:20Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.10000000149011612, + "z": 0.25 + }, + "id": "{d2cd5cb1-cc1c-48b5-a190-b25279ca46aa}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_green.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.0667724609375, + "y": 0.18121337890625, + "z": 0.155120849609375 + }, + "queryAACube": { + "scale": 0.28722813725471497, + "x": -0.076841607689857483, + "y": 0.037599310278892517, + "z": 0.011506780982017517 + }, + "rotation": { + "w": 0.9612659215927124, + "x": -1.8873581211664714e-05, + "y": 0.27562269568443298, + "z": -1.0461797501193359e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:48:20Z", + "dimensions": { + "x": 0.029999999329447746, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{168a3f35-29f3-4a5c-92e5-868ce8476052}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.08154296875, + "y": 0.256256103515625, + "z": 0.1437225341796875 + }, + "queryAACube": { + "scale": 0.25670996308326721, + "x": -0.046812012791633606, + "y": 0.12790112197399139, + "z": 0.015367552638053894 + }, + "rotation": { + "w": 0.96592974662780762, + "x": -1.8688122509047389e-05, + "y": 0.25880429148674011, + "z": -1.0789593034132849e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:48:20Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.10000000149011612, + "z": 0.25 + }, + "id": "{8df113eb-303f-461a-914b-1ff86083af28}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_green.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.0667724609375, + "y": 0.093353271484375, + "z": 0.155120849609375 + }, + "queryAACube": { + "scale": 0.28722813725471497, + "x": -0.076841607689857483, + "y": -0.050260797142982483, + "z": 0.011506780982017517 + }, + "rotation": { + "w": 0.9612659215927124, + "x": -1.8873581211664714e-05, + "y": 0.27562269568443298, + "z": -1.0461797501193359e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T22:48:20Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.10000000149011612, + "z": 0.25 + }, + "id": "{6126fb1e-5702-4ea5-9840-fc93534a284c}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_green.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.0667724609375, + "y": 0, + "z": 0.155120849609375 + }, + "queryAACube": { + "scale": 0.28722813725471497, + "x": -0.076841607689857483, + "y": -0.14361406862735748, + "z": 0.011506780982017517 + }, + "rotation": { + "w": 0.9612659215927124, + "x": -1.8873581211664714e-05, + "y": 0.27562269568443298, + "z": -1.0461797501193359e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + } + ], + "Version": 57 +} diff --git a/unpublishedScripts/DomainContent/Home/blocky/newblocks5.json b/unpublishedScripts/DomainContent/Home/blocky/newblocks5.json new file mode 100644 index 0000000000..88ccb23c44 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/blocky/newblocks5.json @@ -0,0 +1,175 @@ +{ + "Entities": [ + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T23:42:57Z", + "dimensions": { + "x": 0.029999999329447746, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{f89e9217-6048-4ad8-a9fa-80404931864c}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.0689697265625, + "y": 0.336700439453125, + "z": 0.02086639404296875 + }, + "queryAACube": { + "scale": 0.25670996308326721, + "x": -0.059385254979133606, + "y": 0.20834545791149139, + "z": -0.10748858749866486 + }, + "rotation": { + "w": 0.66232722997665405, + "x": 0.66232722997665405, + "y": 0.24763399362564087, + "z": -0.24763399362564087 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T23:42:57Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.10000000149011612, + "z": 0.25 + }, + "id": "{e065e9c3-b722-4ad3-bbd4-3f12efb530f5}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_green.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.0335693359375, + "y": 0.179962158203125, + "z": 0.0505828857421875 + }, + "queryAACube": { + "scale": 0.28722813725471497, + "x": -0.11004473268985748, + "y": 0.036348089575767517, + "z": -0.093031182885169983 + }, + "rotation": { + "w": 0.88294041156768799, + "x": -6.3091447373153642e-06, + "y": -0.46948501467704773, + "z": -2.0636278350139037e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T23:42:57Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{f1367c6c-744a-4ee2-9a6f-ffe89fe67e7e}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0, + "y": 0, + "z": 0.03073883056640625 + }, + "queryAACube": { + "scale": 0.27386128902435303, + "x": -0.13693064451217651, + "y": -0.13693064451217651, + "z": -0.10619181394577026 + }, + "rotation": { + "w": 0.67456501722335815, + "x": 0.67456501722335815, + "y": 0.21204251050949097, + "z": -0.21204251050949097 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T23:42:57Z", + "dimensions": { + "x": 0.10000000149011612, + "y": 0.05000000074505806, + "z": 0.25 + }, + "id": "{73c2bd96-e77d-4ba2-908b-1ee8ca7c814c}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.02685546875, + "y": 0, + "z": 0.0877532958984375 + }, + "queryAACube": { + "scale": 0.27386128902435303, + "x": -0.11007517576217651, + "y": -0.13693064451217651, + "z": -0.049177348613739014 + }, + "rotation": { + "w": 0.67456501722335815, + "x": 0.67456501722335815, + "y": 0.21204251050949097, + "z": -0.21204251050949097 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + }, + { + "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", + "collisionless": 1, + "created": "2016-05-23T23:42:57Z", + "dimensions": { + "x": 0.05000000074505806, + "y": 0.05000000074505806, + "z": 0.05000000074505806 + }, + "id": "{28fd21e4-2138-4ca1-875b-1d35cbefa6b8}", + "ignoreForCollisions": 1, + "modelURL": "atp:/kineticObjects/blocks/planky_natural.fbx", + "name": "home_model_blocky_block", + "position": { + "x": 0.0506591796875, + "y": 0.485748291015625, + "z": 0 + }, + "queryAACube": { + "scale": 0.086602538824081421, + "x": 0.0073579102754592896, + "y": 0.4424470067024231, + "z": -0.04330126941204071 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shapeType": "box", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"hifiHomeKey\":{\"reset\":true}}" + } + ], + "Version": 57 +} diff --git a/unpublishedScripts/DomainContent/Home/blocky/singleSpawner.js b/unpublishedScripts/DomainContent/Home/blocky/singleSpawner.js new file mode 100644 index 0000000000..fc6650ac6a --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/blocky/singleSpawner.js @@ -0,0 +1,27 @@ +// +// +// Created by The Content Team 4/10/216 +// Copyright 2016 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 blockyPath = 'atp:/blocky/wrapper.js'; +Script.include(blockyPath); +var center = Vec3.sum(Vec3.sum(MyAvatar.position, { + x: 0, + y: 0.5, + z: 0 +}), Vec3.multiply(1, Quat.getFront(Camera.getOrientation()))); +var blocky = new BlockyGame(center, { + x: 0, + y: 0, + z: 0 +}); + +Script.scriptEnding.connect(function() { + blocky.cleanup() +}) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/blocky/wrapper.js b/unpublishedScripts/DomainContent/Home/blocky/wrapper.js new file mode 100644 index 0000000000..5ae7d4592e --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/blocky/wrapper.js @@ -0,0 +1,51 @@ +// createPingPongGun.js +// +// Script Type: Entity Spawner +// Created by James B. Pollack on 9/30/2015 +// Copyright 2015 High Fidelity, Inc. +// +// This script creates a gun that shoots ping pong balls when you pull the trigger on a hand controller. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +BlockyGame = function(spawnPosition, spawnRotation) { + + var scriptURL = "atp:/blocky/blocky.js"; + + var blockyProps = { + type: 'Model', + modelURL:'atp:/blocky/swiper.fbx', + name: 'home_box_blocky_resetter', + dimensions: { + x: 0.2543, + y: 0.3269, + z: 0.4154 + }, + rotation:Quat.fromPitchYawRollDegrees(-9.5165,-147.3687,16.6577), + script: scriptURL, + userData: JSON.stringify({ + "grabbableKey": { + "wantsTrigger": true + }, + 'hifiHomeKey': { + 'reset': true + } + }), + dynamic: false, + position: spawnPosition + }; + + var blocky = Entities.addEntity(blockyProps); + + function cleanup() { + print('BLOCKY CLEANUP!') + Entities.deleteEntity(blocky); + } + + this.cleanup = cleanup; + + print('HOME CREATED BLOCKY GAME BLOCK 1') + +} \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/hoverGame/hoverBall.js b/unpublishedScripts/DomainContent/Home/hoverGame/hoverBall.js new file mode 100644 index 0000000000..b4072d4b69 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/hoverGame/hoverBall.js @@ -0,0 +1,60 @@ +(function() { + var _this; + HoverBall = function() { + _this = this; + } + + var MIN_DISTANCE_THRESHOLD = 0.075; + + var CENTER_POINT_LOCATION = { + x: 0, + y: 0, + z: 0 + }; + + HoverBall.prototype = { + preload: function(entityID) { + this.entityID = entityID; + + }, + unload: function() { + + }, + startDistanceGrab: function() { + + }, + continueDistantGrab: function() { + var position = Entities.getEntityProperties(_this.entityID).position; + var distanceFromCenterPoint = Vec3.distance(position, CENTER_POINT_LOCATION); + if (distanceFromCenterPoint < MIN_DISTANCE_THRESHOLD) { + + _this.turnOnGlow(); + } else { + _this.turnOffGlow(); + } + }, + releaseGrab: function() { + _this.turnOffGlow(); + }, + turnOnGlow: function() { + + }, + turnOffGlow: function() { + + }, + findHoverContainer: function() { + var position = Entities.getEntityProperties(_this.entityID).position; + var results = Entities.findEntities(position, 3); + results.forEach(function(item) { + var props = Entities.getEntityProperties(item); + if (props.name.indexOf('hoverGame_container') > -1) { + return item + } + }) + }, + + } + + return new HoverBall(); + +}) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/hoverGame/hoverContainer.js b/unpublishedScripts/DomainContent/Home/hoverGame/hoverContainer.js new file mode 100644 index 0000000000..3391879793 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/hoverGame/hoverContainer.js @@ -0,0 +1,28 @@ +(function() { + + HoverContainer = function() { + + } + + HoverContainer.prototype = { + preload: function(entityID) { + this.entityID = entityID; + + var data = { + action: 'add', + id: this.entityID + }; + Messages.sendLocalMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)) + }, + unload: function() { + var data = { + action: 'remove', + id: this.entityID + }; + Messages.sendLocalMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)) + } + } + + return new HoverContainer(); + +}) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/hoverGame/wrapper.js b/unpublishedScripts/DomainContent/Home/hoverGame/wrapper.js new file mode 100644 index 0000000000..a176e359fa --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/hoverGame/wrapper.js @@ -0,0 +1,80 @@ +// createPingPongGun.js +// +// Script Type: Entity Spawner +// Created by James B. Pollack on 9/30/2015 +// Copyright 2015 High Fidelity, Inc. +// +// This script creates a gun that shoots ping pong balls when you pull the trigger on a hand controller. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +//var position = {x:1098.4813,y:461.6781,z:-71.3820} +// var dimensions = {x:1.0233,y:3.1541,z:0.8684} +HoverGame = function(spawnPosition, spawnRotation) { + + var scriptURL = "atp:/hoverGame/hoverInner.js"; + + var hoverContainerProps = { + type: 'Model', + modelURL: 'atp:/hoverGame/hover.fbx', + name: 'home_model_hoverGame_container', + dimensions: {x:1.0233,y:3.1541,z:0.8684}, + compoundShapeURL: 'atp:/hoverGame/hoverHull.obj', + rotation: spawnRotation, + script: scriptURL, + userData: JSON.stringify({ + "grabbableKey": { + "grabbable":false + }, + 'hifiHomeKey': { + 'reset': true + } + }), + dynamic: false, + position: spawnPosition + }; + + var hoverBall = { + type: 'Sphere', + name: 'home_model_hoverGame_sphere', + dimensions: { + x: 0.25, + y: 0.25, + z: 0.25, + }, + compoundShapeURL: 'atp:/hoverGame/hoverHull.obj', + rotation: spawnRotation, + script: scriptURL, + userData: JSON.stringify({ + 'hifiHomeKey': { + 'reset': true + } + }), + dynamic: true, + gravity:{ + x:0, + y:-9.8, + z:0 + }, + position: spawnPosition, + userData:JSON.stringify({ + grabKey:{ + shouldCollideWith:'static' + } + }) + }; + + var hoverContainer = Entities.addEntity(hoverContainerProps); + var hoverBall = Entities.addEntity(hoverContainerProps); + + function cleanup() { + print('HOVER GAME CLEANUP!') + Entities.deleteEntity(hoverInner); + } + + this.cleanup = cleanup; + + print('HOME CREATED HOVER GAME') + +} \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/kineticObjects/bench.json b/unpublishedScripts/DomainContent/Home/kineticObjects/bench.json new file mode 100644 index 0000000000..6ad65af88d --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/kineticObjects/bench.json @@ -0,0 +1,37 @@ +{ + "Entities": [{ + "name": "home_model_bench", + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/kineticObjects/bench/Bench.obj", + "created": "2016-05-17T19:27:31Z", + "dimensions": { + "x": 2.3647537231445312, + "y": 0.51260757446289062, + "z": 0.49234861135482788 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{733c3f02-a98a-4876-91dc-320a26a07090}", + "modelURL": "atp:/kineticObjects/bench/Bench.fbx", + "queryAACube": { + "scale": 2.4692578315734863, + "x": -1.2346289157867432, + "y": -1.2346289157867432, + "z": -1.2346289157867432 + }, + "rotation": { + "w": 0.88613712787628174, + "x": -0.0015716552734375, + "y": -0.46337074041366577, + "z": -1.52587890625e-05 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true}}" + }], + "Version": 57 +} \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/kineticObjects/blocks.json b/unpublishedScripts/DomainContent/Home/kineticObjects/blocks.json deleted file mode 100644 index 08331169de..0000000000 --- a/unpublishedScripts/DomainContent/Home/kineticObjects/blocks.json +++ /dev/null @@ -1,778 +0,0 @@ -{ - "Entities": [{ - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.05000000074505806, - "y": 0.05000000074505806, - "z": 0.05000000074505806 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{5371218c-e05b-49da-ac70-81f1f76c55ea}", - "modelURL": "atp:/kineticObjects/blocks/planky_natural.fbx", - "name": "home_model_block", - "position": { - "x": 0.69732666015625, - "y": 0.1600341796875, - "z": 0.28265380859375 - }, - "queryAACube": { - "scale": 0.086602538824081421, - "x": 0.6540253758430481, - "y": 0.11673291027545929, - "z": 0.23935253918170929 - }, - "rotation": { - "w": 0.996917724609375, - "x": -0.0001678466796875, - "y": 0.078294038772583008, - "z": -0.0003204345703125 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.05000000074505806, - "y": 0.05000000074505806, - "z": 0.05000000074505806 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{ccc1198a-a501-48b8-959a-68297258aea7}", - "modelURL": "atp:/kineticObjects/blocks/planky_natural.fbx", - "name": "home_model_block", - "position": { - "x": 0.47344970703125, - "y": 0.06005859375, - "z": 0.30706787109375 - }, - "queryAACube": { - "scale": 0.086602538824081421, - "x": 0.4301484227180481, - "y": 0.01675732433795929, - "z": 0.2637665867805481 - }, - "rotation": { - "w": 0.70644688606262207, - "x": 0.70638585090637207, - "y": -0.030960559844970703, - "z": 0.031174182891845703 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.10000000149011612, - "y": 0.05000000074505806, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{3aaf5dd5-16d8-4852-880d-8256de68de19}", - "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", - "name": "home_model_block", - "position": { - "x": 0.2613525390625, - "y": 0.01007080078125, - "z": 0.359466552734375 - }, - "queryAACube": { - "scale": 0.27386128902435303, - "x": 0.12442189455032349, - "y": -0.12685984373092651, - "z": 0.22253590822219849 - }, - "rotation": { - "w": -4.57763671875e-05, - "x": 0.35637450218200684, - "y": -4.57763671875e-05, - "z": 0.93432521820068359 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.029999999329447746, - "y": 0.05000000074505806, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{0474a29f-c45b-4d42-ae95-0c0bd1e6c501}", - "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", - "name": "home_model_block", - "position": { - "x": 0.30487060546875, - "y": 0.01007080078125, - "z": 0.188262939453125 - }, - "queryAACube": { - "scale": 0.25670996308326721, - "x": 0.17651562392711639, - "y": -0.11828418076038361, - "z": 0.059907957911491394 - }, - "rotation": { - "w": 0.99496448040008545, - "x": 1.52587890625e-05, - "y": 0.10020601749420166, - "z": 0.0002288818359375 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.10000000149011612, - "y": 0.10000000149011612, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{53e06851-8346-45ac-bdb5-0a74c99b5bd5}", - "modelURL": "atp:/kineticObjects/blocks/planky_green.fbx", - "name": "home_model_block", - "position": { - "x": 0.40277099609375, - "y": 0.035064697265625, - "z": 0.254364013671875 - }, - "queryAACube": { - "scale": 0.28722813725471497, - "x": 0.25915694236755371, - "y": -0.10854937136173248, - "z": 0.11074994504451752 - }, - "rotation": { - "w": 0.70650792121887207, - "x": -0.031143665313720703, - "y": -0.031143665313720703, - "z": 0.70632481575012207 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.029999999329447746, - "y": 0.05000000074505806, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{38bcb70d-e384-4b60-878a-e34d4830f045}", - "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", - "name": "home_model_block", - "position": { - "x": 0.54962158203125, - "y": 0.010040283203125, - "z": 0.294647216796875 - }, - "queryAACube": { - "scale": 0.25670996308326721, - "x": 0.42126661539077759, - "y": -0.11831469833850861, - "z": 0.16629223525524139 - }, - "rotation": { - "w": 0.999969482421875, - "x": -7.62939453125e-05, - "y": -0.0037384629249572754, - "z": 0.0001068115234375 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.10000000149011612, - "y": 0.05000000074505806, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{fa8f8bbc-5bd0-4121-985d-75ce2f68eba1}", - "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", - "name": "home_model_block", - "position": { - "x": 0.52001953125, - "y": 0.01007080078125, - "z": 0 - }, - "queryAACube": { - "scale": 0.27386128902435303, - "x": 0.38308888673782349, - "y": -0.12685984373092651, - "z": -0.13693064451217651 - }, - "rotation": { - "w": 0.9861142635345459, - "x": -7.62939453125e-05, - "y": -0.16603344678878784, - "z": -7.62939453125e-05 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.029999999329447746, - "y": 0.05000000074505806, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{d4b8582b-b707-453c-89c6-65e358da5cd7}", - "modelURL": "atp:/kineticObjects/blocks/planky_yellow.fbx", - "name": "home_model_block", - "position": { - "x": 0.66876220703125, - "y": 0, - "z": 0.012939453125 - }, - "queryAACube": { - "scale": 0.25670996308326721, - "x": 0.54040724039077759, - "y": -0.12835498154163361, - "z": -0.11541552841663361 - }, - "rotation": { - "w": 0.70589756965637207, - "x": 0.036270737648010254, - "y": -0.036331713199615479, - "z": -0.70647746324539185 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.10000000149011612, - "y": 0.05000000074505806, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{3b5b53fb-7ee5-44eb-9b81-8de8a525c433}", - "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", - "name": "home_model_block", - "position": { - "x": 0.83819580078125, - "y": 0.135040283203125, - "z": 0.261627197265625 - }, - "queryAACube": { - "scale": 0.27386128902435303, - "x": 0.70126515626907349, - "y": -0.0018903613090515137, - "z": 0.12469655275344849 - }, - "rotation": { - "w": 0.69332420825958252, - "x": 0.13832306861877441, - "y": -0.13841456174850464, - "z": -0.69353783130645752 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.10000000149011612, - "y": 0.10000000149011612, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{0d1f27e9-7e74-4263-9428-8c8f7aac94a6}", - "modelURL": "atp:/kineticObjects/blocks/planky_green.fbx", - "name": "home_model_block", - "position": { - "x": 0.61773681640625, - "y": 0.0350341796875, - "z": 0.265533447265625 - }, - "queryAACube": { - "scale": 0.28722813725471497, - "x": 0.47412276268005371, - "y": -0.10857988893985748, - "z": 0.12191937863826752 - }, - "rotation": { - "w": 0.9998779296875, - "x": -4.57763671875e-05, - "y": -0.014206171035766602, - "z": 1.52587890625e-05 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.10000000149011612, - "y": 0.10000000149011612, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{79ea518f-aac3-45ff-b22d-6d295b3c9e87}", - "modelURL": "atp:/kineticObjects/blocks/planky_green.fbx", - "name": "home_model_block", - "position": { - "x": 0.7188720703125, - "y": 0.08502197265625, - "z": 0.26397705078125 - }, - "queryAACube": { - "scale": 0.28722813725471497, - "x": 0.57525801658630371, - "y": -0.058592095971107483, - "z": 0.12036298215389252 - }, - "rotation": { - "w": 0.99993896484375, - "x": -4.57763671875e-05, - "y": -0.0099946856498718262, - "z": -0.0001068115234375 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.05000000074505806, - "y": 0.05000000074505806, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{ff470ff9-c889-4893-a25f-80895bff0e9a}", - "modelURL": "atp:/kineticObjects/blocks/planky_blue.fbx", - "name": "home_model_block", - "position": { - "x": 0.74981689453125, - "y": 0.010040283203125, - "z": 0.258575439453125 - }, - "queryAACube": { - "scale": 0.25980761647224426, - "x": 0.61991310119628906, - "y": -0.11986352503299713, - "z": 0.12867163121700287 - }, - "rotation": { - "w": 0.99993896484375, - "x": -4.57763671875e-05, - "y": -0.010666072368621826, - "z": -0.0003814697265625 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.10000000149011612, - "y": 0.10000000149011612, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{b5319f85-603d-436b-8bbe-fc9f798ca738}", - "modelURL": "atp:/kineticObjects/blocks/planky_green.fbx", - "name": "home_model_block", - "position": { - "x": 0.8824462890625, - "y": 0.0350341796875, - "z": 0.281097412109375 - }, - "queryAACube": { - "scale": 0.28722813725471497, - "x": 0.73883223533630371, - "y": -0.10857988893985748, - "z": 0.13748334348201752 - }, - "rotation": { - "w": 0.999847412109375, - "x": -4.57763671875e-05, - "y": -0.016922235488891602, - "z": 1.52587890625e-05 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.05000000074505806, - "y": 0.05000000074505806, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{944a4616-8dac-4d6a-a92b-49fa98514416}", - "modelURL": "atp:/kineticObjects/blocks/planky_blue.fbx", - "name": "home_model_block", - "position": { - "x": 0.963623046875, - "y": 0.010009765625, - "z": 0.25885009765625 - }, - "queryAACube": { - "scale": 0.25980761647224426, - "x": 0.83371925354003906, - "y": -0.11989404261112213, - "z": 0.12894628942012787 - }, - "rotation": { - "w": 0.999847412109375, - "x": -4.57763671875e-05, - "y": -0.017379999160766602, - "z": 1.52587890625e-05 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.05000000074505806, - "y": 0.05000000074505806, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{ea6a1038-7047-4a1e-bdbd-076d6e41508c}", - "modelURL": "atp:/kineticObjects/blocks/planky_blue.fbx", - "name": "home_model_block", - "position": { - "x": 0.49188232421875, - "y": 0.010040283203125, - "z": 0.27752685546875 - }, - "queryAACube": { - "scale": 0.25980761647224426, - "x": 0.36197853088378906, - "y": -0.11986352503299713, - "z": 0.14762304723262787 - }, - "rotation": { - "w": 0.99798583984375, - "x": -4.57763671875e-05, - "y": -0.063248634338378906, - "z": 4.57763671875e-05 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.05000000074505806, - "y": 0.05000000074505806, - "z": 0.05000000074505806 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{ff65f5dd-2d53-4127-86da-05156a42946d}", - "modelURL": "atp:/kineticObjects/blocks/planky_natural.fbx", - "name": "home_model_block", - "position": { - "x": 0, - "y": 0.010101318359375, - "z": 0.353302001953125 - }, - "queryAACube": { - "scale": 0.086602538824081421, - "x": -0.04330126941204071, - "y": -0.03319995105266571, - "z": 0.3100007176399231 - }, - "rotation": { - "w": 0.55049967765808105, - "x": 0.44353401660919189, - "y": -0.44359505176544189, - "z": -0.55083543062210083 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.10000000149011612, - "y": 0.05000000074505806, - "z": 0.25 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{3a9acd14-f754-4c70-b294-9f622c000785}", - "modelURL": "atp:/kineticObjects/blocks/planky_red.fbx", - "name": "home_model_block", - "position": { - "x": 0.88006591796875, - "y": 0.010040283203125, - "z": 0.05718994140625 - }, - "queryAACube": { - "scale": 0.27386128902435303, - "x": 0.74313527345657349, - "y": -0.12689036130905151, - "z": -0.079740703105926514 - }, - "rotation": { - "w": 0.86965739727020264, - "x": -4.57763671875e-05, - "y": -0.4936751127243042, - "z": -4.57763671875e-05 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }, { - "collisionSoundURL": "atp:/kineticObjects/blocks/ToyWoodBlock.L.wav", - "collisionsWillMove": 1, - "created": "2016-03-23T21:29:36Z", - "dimensions": { - "x": 0.05000000074505806, - "y": 0.05000000074505806, - "z": 0.05000000074505806 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "velocity": { - "x": 0, - "y": -0.1, - "z": 0 - }, - "id": "{bb014301-247b-44d0-8b09-b830fea4439e}", - "modelURL": "atp:/kineticObjects/blocks/planky_natural.fbx", - "name": "home_model_block", - "position": { - "x": 0.80487060546875, - "y": 0.010040283203125, - "z": 0.19500732421875 - }, - "queryAACube": { - "scale": 0.086602538824081421, - "x": 0.7615693211555481, - "y": -0.03326098620891571, - "z": 0.15170605480670929 - }, - "rotation": { - "w": 0.70440220832824707, - "x": 0.060776710510253906, - "y": -0.060868263244628906, - "z": -0.70455479621887207 - }, - "shapeType": "box", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }], - "Version": 57 -} \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/kineticObjects/blueChair.json b/unpublishedScripts/DomainContent/Home/kineticObjects/blueChair.json deleted file mode 100644 index 2556cd6223..0000000000 --- a/unpublishedScripts/DomainContent/Home/kineticObjects/blueChair.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "Entities": [{ - "collisionsWillMove": 1, - "compoundShapeURL": "atp:/kineticObjects/blueChair/Comfy_Chair_Blue_hull.obj", - "created": "2016-03-29T17:37:52Z", - "dimensions": { - "x": 1.1764, - "y": 1.4557, - "z": 1.2657 - }, - "dynamic": 1, - "gravity": { - "x": 0, - "y": -10, - "z": 0 - }, - "id": "{51a44c3a-ec4a-4c79-8034-aeb5c45660b5}", - "modelURL": "atp:/kineticObjects/blueChair/Comfy_Chair_Blue.fbx", - "name": "home_model_comfyChair", - "queryAACube": { - "scale": 1.9147497415542603, - "x": -0.95737487077713013, - "y": -0.95737487077713013, - "z": -0.95737487077713013 - }, - "rotation": { - "w": 0.46746015548706055, - "x": -0.0017547607421875, - "y": 0.88400089740753174, - "z": 0.0024261474609375 - }, - "shapeType": "compound", - "type": "Model", - "userData": "{\"hifiHomeKey\":{\"reset\":true}}" - }], - "Version": 57 -} \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/kineticObjects/dressingRoomBricabrac.json b/unpublishedScripts/DomainContent/Home/kineticObjects/dressingRoomBricabrac.json new file mode 100644 index 0000000000..c87fd7bc98 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/kineticObjects/dressingRoomBricabrac.json @@ -0,0 +1,554 @@ +{ + "Entities": [{ + "name": "home_model_dressing_room_bricabrac", + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/kineticObjects/skully/skullyhull.obj", + "created": "2016-05-16T19:34:28Z", + "dimensions": { + "x": 0.19080814719200134, + "y": 0.20009028911590576, + "z": 0.21198928356170654 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -6, + "z": 0 + }, + "id": "{cef37b0a-bb3d-4549-a645-f3a934f82148}", + "modelURL": "atp:/kineticObjects/skully/skully.fbx", + "position": { + "x": 0.254150390625, + "y": 0.4500732421875, + "z": 0.26517486572265625 + }, + "queryAACube": { + "scale": 0.34840109944343567, + "x": 0.079949840903282166, + "y": 0.27587270736694336, + "z": 0.090974316000938416 + }, + "rotation": { + "w": 0.11515986919403076, + "x": -0.087693572044372559, + "y": 0.88818192481994629, + "z": -0.43608760833740234 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/dressingRoom/trump.obj", + "created": "2016-05-18T00:02:46Z", + "dimensions": { + "x": 0.25387090444564819, + "y": 0.28642392158508301, + "z": 0.31468403339385986 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{6483a752-5ba0-4a02-bee5-3290781dfef8}", + "modelURL": "atp:/dressingRoom/trump.fbx", + "position": { + "x": 0.3887939453125, + "y": 0.879974365234375, + "z": 0.2674407958984375 + }, + "queryAACube": { + "scale": 0.49549484252929688, + "x": 0.14104652404785156, + "y": 0.63222694396972656, + "z": 0.019693374633789062 + }, + "rotation": { + "w": 0.58565652370452881, + "x": -0.039444565773010254, + "y": 0.73434042930603027, + "z": -0.34084075689315796 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/dressingRoom/dreads.obj", + "created": "2016-05-18T00:02:46Z", + "dimensions": { + "x": 0.30918264389038086, + "y": 0.31221473217010498, + "z": 0.29840445518493652 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{2f2fb50b-35f0-4818-9cfa-e29f7aeb7a1f}", + "modelURL": "atp:/dressingRoom/dreads.fbx", + "position": { + "x": 0.5389404296875, + "y": 0.52008056640625, + "z": 0.09856414794921875 + }, + "queryAACube": { + "scale": 0.53114700317382812, + "x": 0.27336692810058594, + "y": 0.25450706481933594, + "z": -0.16700935363769531 + }, + "rotation": { + "w": 0.85949492454528809, + "x": 0.051773905754089355, + "y": -0.41289389133453369, + "z": 0.29680323600769043 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/kineticObjects/tophat/tophat.obj", + "created": "2016-05-16T16:59:22Z", + "dimensions": { + "x": 0.38495281338691711, + "y": 0.26857495307922363, + "z": 0.39648750424385071 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{8cff801b-cb50-47d2-a1e8-4f7ee245d2ee}", + "modelURL": "atp:/kineticObjects/tophat/tophat.fbx", + "position": { + "x": 0, + "y": 0.541900634765625, + "z": 0.4959869384765625 + }, + "queryAACube": { + "scale": 0.61442941427230835, + "x": -0.30721470713615417, + "y": 0.23468592762947083, + "z": 0.18877223134040833 + }, + "rotation": { + "w": 0.92727553844451904, + "x": -0.18382543325424194, + "y": 0.32610058784484863, + "z": 0.0003204345703125 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/kineticObjects/hair/m2.obj", + "created": "2016-05-16T17:12:28Z", + "dimensions": { + "x": 0.094467177987098694, + "y": 0.079604864120483398, + "z": 0.029766213148832321 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{0805851a-4ef1-4075-b4b3-5692ca7d6352}", + "modelURL": "atp:/kineticObjects/hair/m3.fbx", + "position": { + "x": 0.0111083984375, + "y": 0.00689697265625, + "z": 0.45806884765625 + }, + "queryAACube": { + "scale": 0.12707087397575378, + "x": -0.052427038550376892, + "y": -0.056638464331626892, + "z": 0.39453339576721191 + }, + "rotation": { + "w": 0.84777605533599854, + "x": 0.43642330169677734, + "y": 0.26695656776428223, + "z": -0.13978791236877441 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/dressingRoom/rectangleFrames.obj", + "created": "2016-05-18T15:50:52Z", + "dimensions": { + "x": 0.18497957289218903, + "y": 0.048084259033203125, + "z": 0.14581345021724701 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{692df470-2a76-4d5b-971b-257291ff67f7}", + "modelURL": "atp:/dressingRoom/rectangleFrames.fbx", + "name": "rectangleFrames.fbx", + "position": { + "x": 0.3701171875, + "y": 0.062652587890625, + "z": 0.35735321044921875 + }, + "queryAACube": { + "scale": 0.24039779603481293, + "x": 0.24991828203201294, + "y": -0.057546310126781464, + "z": 0.23715430498123169 + }, + "rotation": { + "w": -0.084428191184997559, + "x": -0.10658425092697144, + "y": 0.9651484489440918, + "z": -0.2235599160194397 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/kineticObjects/hair/b3.obj", + "created": "2016-05-16T17:10:17Z", + "dimensions": { + "x": 0.19054701924324036, + "y": 0.16880631446838379, + "z": 0.11410932987928391 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{32926ddc-7c3b-49ca-97da-56e69af211af}", + "modelURL": "atp:/kineticObjects/hair/b3.fbx", + "position": { + "x": 0.75, + "y": 0.0643310546875, + "z": 0 + }, + "queryAACube": { + "scale": 0.27897074818611145, + "x": 0.61051464080810547, + "y": -0.075154319405555725, + "z": -0.13948537409305573 + }, + "rotation": { + "w": -0.26207369565963745, + "x": 0.055374979972839355, + "y": 0.93298232555389404, + "z": 0.24034488201141357 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/kineticObjects/hair/b1.obj", + "created": "2016-05-16T17:05:55Z", + "dimensions": { + "x": 0.18918535113334656, + "y": 0.15465652942657471, + "z": 0.11267256736755371 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{c412654f-9feb-489d-91f9-6f4eb369afe7}", + "modelURL": "atp:/kineticObjects/hair/b1.fbx", + "position": { + "x": 0.55712890625, + "y": 0.0443115234375, + "z": 0.0774078369140625 + }, + "queryAACube": { + "scale": 0.2690814733505249, + "x": 0.42258816957473755, + "y": -0.090229213237762451, + "z": -0.057132899761199951 + }, + "rotation": { + "w": -0.26680397987365723, + "x": 0.16276800632476807, + "y": 0.81954681873321533, + "z": 0.4802471399307251 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/kineticObjects/hair/m3.obj", + "created": "2016-05-16T17:12:28Z", + "dimensions": { + "x": 0.15590831637382507, + "y": 0.057838320732116699, + "z": 0.033922493457794189 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{c2df6722-e970-4c36-bf42-b117e2bc13c4}", + "modelURL": "atp:/kineticObjects/hair/m3.fbx", + "position": { + "x": 0.1549072265625, + "y": 0.006439208984375, + "z": 0.34037017822265625 + }, + "queryAACube": { + "scale": 0.16971567273139954, + "x": 0.070049390196800232, + "y": -0.078418627381324768, + "z": 0.25551235675811768 + }, + "rotation": { + "w": -0.2230105996131897, + "x": 0.19484245777130127, + "y": 0.73751425743103027, + "z": 0.60689711570739746 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/dressingRoom/Elvis.obj", + "created": "2016-05-17T23:58:24Z", + "dimensions": { + "x": 0.22462925314903259, + "y": 0.32056856155395508, + "z": 0.32186508178710938 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{5fe0d835-e52b-432b-8ffe-2e7467613974}", + "modelURL": "atp:/dressingRoom/Elvis.fbx", + "position": { + "x": 0.6776123046875, + "y": 0.89739990234375, + "z": 0.036468505859375 + }, + "queryAACube": { + "scale": 0.50677376985549927, + "x": 0.42422541975975037, + "y": 0.64401304721832275, + "z": -0.21691837906837463 + }, + "rotation": { + "w": 0.76330208778381348, + "x": 0.26140224933624268, + "y": -0.43398183584213257, + "z": 0.40090024471282959 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/dressingRoom/roundFrames.obj", + "created": "2016-05-18T15:55:14Z", + "dimensions": { + "x": 0.18497957289218903, + "y": 0.061892151832580566, + "z": 0.14581345021724701 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{2706fd7c-9f99-4e11-aebe-4c7274d29d69}", + "modelURL": "atp:/dressingRoom/roundFrames.fbx", + "name": "roundFrames.fbx", + "position": { + "x": 0.1690673828125, + "y": 0.08319091796875, + "z": 0.50496673583984375 + }, + "queryAACube": { + "scale": 0.24353571236133575, + "x": 0.047299526631832123, + "y": -0.038576938211917877, + "z": 0.38319888710975647 + }, + "rotation": { + "w": -0.27809566259384155, + "x": -0.059800088405609131, + "y": 0.93838405609130859, + "z": -0.19615471363067627 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/kineticObjects/hair/m1.obj", + "created": "2016-05-16T17:10:17Z", + "dimensions": { + "x": 0.10473950952291489, + "y": 0.044521570205688477, + "z": 0.032888296991586685 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{448c53c2-bf1e-44f5-b28f-06da33a4145d}", + "modelURL": "atp:/kineticObjects/hair/m1.fbx", + "position": { + "x": 0.29052734375, + "y": 0, + "z": 0.254638671875 + }, + "queryAACube": { + "scale": 0.11846592277288437, + "x": 0.23129437863826752, + "y": -0.059232961386442184, + "z": 0.19540570676326752 + }, + "rotation": { + "w": -0.24409854412078857, + "x": -0.17210650444030762, + "y": 0.76794075965881348, + "z": -0.56667429208755493 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/kineticObjects/hair/b2.obj", + "created": "2016-05-16T17:05:55Z", + "dimensions": { + "x": 0.12604480981826782, + "y": 0.090894937515258789, + "z": 0.069697588682174683 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{33d3ba77-d2c9-4a5f-ba1c-f6f731d05221}", + "modelURL": "atp:/kineticObjects/hair/b3.fbx", + "position": { + "x": 0.4130859375, + "y": 0.030181884765625, + "z": 0.182342529296875 + }, + "queryAACube": { + "scale": 0.1703142374753952, + "x": 0.3279288113117218, + "y": -0.054975233972072601, + "z": 0.097185410559177399 + }, + "rotation": { + "w": -0.2856946587562561, + "x": 0.17711150646209717, + "y": 0.86263823509216309, + "z": 0.37792015075683594 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true}}" + }, { + "name": "home_model_dressing_room_bricabrac", + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/kineticObjects/snapback/snapback.obj", + "created": "2016-05-16T17:01:33Z", + "dimensions": { + "x": 0.19942393898963928, + "y": 0.12862896919250488, + "z": 0.30034130811691284 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{26e895b7-4a83-45fe-a3e9-5b6ba5f5717e}", + "modelURL": "atp:/kineticObjects/snapback/snapback.fbx", + "position": { + "x": 0.7891845703125, + "y": 0.492218017578125, + "z": 0.0359649658203125 + }, + "queryAACube": { + "scale": 0.38277959823608398, + "x": 0.59779477119445801, + "y": 0.30082821846008301, + "z": -0.15542483329772949 + }, + "rotation": { + "w": -0.31783014535903931, + "x": -0.04985123872756958, + "y": 0.92556643486022949, + "z": -0.19945067167282104 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true},\"wearable\":{\"joints\":{\"head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"Head\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"hair\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}],\"neck\":[{\"x\":0,\"y\":0,\"z\":0},{\"w\":0,\"x\":0,\"y\":0,\"z\":0}]}}}" + }], + "Version": 57 +} \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/kineticObjects/umbrella.json b/unpublishedScripts/DomainContent/Home/kineticObjects/umbrella.json new file mode 100644 index 0000000000..6ab8f11ddc --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/kineticObjects/umbrella.json @@ -0,0 +1,39 @@ +{ + "Entities": [{ + "name": "home_model_umbrella", + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "collisionsWillMove": 1, + "compoundShapeURL": "atp:/kineticObjects/umbrella/umbrellaopen_ch.obj", + "created": "2016-05-16T22:48:54Z", + "dimensions": { + "x": 1.2649545669555664, + "y": 1.0218980312347412, + "z": 1.2649545669555664 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -5, + "z": 0 + }, + "id": "{59f5cc21-c6e8-4deb-a1e2-0364e82fe062}", + "modelURL": "atp:/kineticObjects/umbrella/umbrella_open.fbx", + "queryAACube": { + "scale": 2.0602173805236816, + "x": -1.0301086902618408, + "y": -1.0301086902618408, + "z": -1.0301086902618408 + }, + "rotation": { + "w": 0.66066992282867432, + "x": -0.2207522988319397, + "y": 0.64501416683197021, + "z": -0.31422901153564453 + }, + "shapeType": "compound", + "type": "Model", + "userData": "{\"hifiHomeKey\":{\"reset\":true}}" + }], + "Version": 57 +} \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/kineticObjects/wrapper.js b/unpublishedScripts/DomainContent/Home/kineticObjects/wrapper.js index 5b0e75dc94..d4512823db 100644 --- a/unpublishedScripts/DomainContent/Home/kineticObjects/wrapper.js +++ b/unpublishedScripts/DomainContent/Home/kineticObjects/wrapper.js @@ -1,24 +1,23 @@ -print('HOME KINETIC INCLUDING WRAPPER') - -var BOOKS_URL = "atp:/kineticObjects/books.json" -var UPPER_BOOKSHELF_URL = "atp:/kineticObjects/upperBookShelf.json" -var LOWER_BOOKSHELF_URL = "atp:/kineticObjects/lowerBookShelf.json" +print('HOME KINETIC INCLUDING WRAPPER'); +var BOOKS_URL = "atp:/kineticObjects/books.json"; +var UPPER_BOOKSHELF_URL = "atp:/kineticObjects/upperBookShelf.json"; +var LOWER_BOOKSHELF_URL = "atp:/kineticObjects/lowerBookShelf.json"; var CHAIR_URL = 'atp:/kineticObjects/deskChair.json'; -var BLUE_CHAIR_URL = 'atp:/kineticObjects/blueChair.json'; - var FRUIT_BOWL_URL = "atp:/kineticObjects/fruit.json" - -var LIVING_ROOM_LAMP_URL = "atp:/kineticObjects/deskLamp.json" +var LIVING_ROOM_LAMP_URL = "atp:/kineticObjects/deskLamp.json"; var TRASHCAN_URL = "atp:/kineticObjects/trashcan.json" var BLOCKS_URL = "atp:/kineticObjects/blocks.json"; -var PLAYA_POSTER_URL = "atp:/kineticObjects/postersPlaya.json" -var CELL_POSTER_URL = "atp:/kineticObjects/postersCell.json" -var STUFF_ON_SHELVES_URL = "atp:/kineticObjects/stuff_on_shelves.json" -var JUNK_URL = "atp:/kineticObjects/junk.json" +var PLAYA_POSTER_URL = "atp:/kineticObjects/postersPlaya.json"; +var CELL_POSTER_URL = "atp:/kineticObjects/postersCell.json"; +var STUFF_ON_SHELVES_URL = "atp:/kineticObjects/stuff_on_shelves.json"; +var JUNK_URL = "atp:/kineticObjects/junk.json"; +var BRICABRAC_URL = "atp:/kineticObjects/dressingRoomBricabrac.json"; +var BENCH_URL = "atp:/kineticObjects/bench.json"; +var UMBRELLA_URL = "atp:/kineticObjects/umbrella.json"; FruitBowl = function(spawnLocation, spawnRotation) { - print('CREATE FRUIT BOWL') + print('CREATE FRUIT BOWL'); var created = []; function create() { @@ -42,7 +41,7 @@ FruitBowl = function(spawnLocation, spawnRotation) { } LivingRoomLamp = function(spawnLocation, spawnRotation) { - print('CREATE LIVING ROOM LAMP') + print('CREATE LIVING ROOM LAMP'); var created = []; function create() { @@ -65,7 +64,7 @@ LivingRoomLamp = function(spawnLocation, spawnRotation) { } UpperBookShelf = function(spawnLocation, spawnRotation) { - print('CREATE UPPER SHELF') + print('CREATE UPPER SHELF'); var created = []; function create() { @@ -89,7 +88,7 @@ UpperBookShelf = function(spawnLocation, spawnRotation) { LowerBookShelf = function(spawnLocation, spawnRotation) { - print('CREATE LOWER SHELF') + print('CREATE LOWER SHELF'); var created = []; function create() { @@ -112,7 +111,7 @@ LowerBookShelf = function(spawnLocation, spawnRotation) { } Chair = function(spawnLocation, spawnRotation) { - print('CREATE CHAIR') + print('CREATE CHAIR'); var created = []; function create() { @@ -134,32 +133,8 @@ Chair = function(spawnLocation, spawnRotation) { this.cleanup = cleanup; } -BlueChair = function(spawnLocation, spawnRotation) { - print('CREATE BLUE CHAIR') - var created = []; - - function create() { - var success = Clipboard.importEntities(BLUE_CHAIR_URL); - - if (success === true) { - created = Clipboard.pasteEntities(spawnLocation) - print('created ' + created); - } - }; - - function cleanup() { - created.forEach(function(obj) { - Entities.deleteEntity(obj); - }) - }; - - create(); - - this.cleanup = cleanup; -} - Trashcan = function(spawnLocation, spawnRotation) { - print('CREATE TRASHCAN') + print('CREATE TRASHCAN'); var created = []; function create() { @@ -183,7 +158,7 @@ Trashcan = function(spawnLocation, spawnRotation) { } Books = function(spawnLocation, spawnRotation) { - print('CREATE BOOKS') + print('CREATE BOOKS'); var created = []; function create() { @@ -206,7 +181,7 @@ Books = function(spawnLocation, spawnRotation) { } Blocks = function(spawnLocation, spawnRotation) { - print('EBL CREATE BLOCKS') + print('EBL CREATE BLOCKS'); var created = []; function create() { @@ -230,7 +205,7 @@ Blocks = function(spawnLocation, spawnRotation) { } PosterCell = function(spawnLocation, spawnRotation) { - print('CREATE CELL POSTER') + print('CREATE CELL POSTER'); var created = []; function create() { @@ -253,7 +228,7 @@ PosterCell = function(spawnLocation, spawnRotation) { } PosterPlaya = function(spawnLocation, spawnRotation) { - print('CREATE PLAYA POSTER') + print('CREATE PLAYA POSTER'); var created = []; function create() { @@ -276,9 +251,10 @@ PosterPlaya = function(spawnLocation, spawnRotation) { } StuffOnShelves = function(spawnLocation, spawnRotation) { - print('CREATE STUFF ON SHELVES') + print('CREATE STUFF ON SHELVES'); var created = []; + function create() { var success = Clipboard.importEntities(STUFF_ON_SHELVES_URL); if (success === true) { @@ -293,17 +269,115 @@ StuffOnShelves = function(spawnLocation, spawnRotation) { }) }; + + + create(); + + this.cleanup = cleanup; +} + +HomeJunk = function(spawnLocation, spawnRotation) { + print('HOME CREATE JUNK'); + var created = []; + function addVelocityDown() { + print('HOME ADDING DOWN VELOCITY TO SHELF ITEMS') + created.forEach(function(obj) { + Entities.editEntity(obj, { + velocity: { + x: 0, + y: -0.1, + z: 0 + } + }); + }) + } + function create() { + var success = Clipboard.importEntities(JUNK_URL); + if (success === true) { + created = Clipboard.pasteEntities(spawnLocation) + print('created ' + created); + addVelocityDown(); + } + }; + + function cleanup() { + created.forEach(function(obj) { + Entities.deleteEntity(obj); + }) + }; + + create(); + + this.cleanup = cleanup; +} + +// Bricabrac = function(spawnLocation, spawnRotation) { +// print('HOME CREATE BRICABRAC'); +// var created = []; + +// function addVelocityDown() { +// print('HOME ADDING DOWN VELOCITY TO DRESSING ROOM ITEMS') +// created.forEach(function(obj) { +// Entities.editEntity(obj, { +// velocity: { +// x: 0, +// y: -0.1, +// z: 0 +// } +// }); +// }) +// } + +// function create() { +// var success = Clipboard.importEntities(BRICABRAC_URL); +// if (success === true) { +// created = Clipboard.pasteEntities(spawnLocation) +// print('created ' + created); +// addVelocityDown(); + +// } +// }; + +// function cleanup() { +// created.forEach(function(obj) { +// Entities.deleteEntity(obj); +// }) +// }; + +// create(); + +// this.cleanup = cleanup; +// } + +Bench = function(spawnLocation, spawnRotation) { + print('HOME CREATE BENCH'); + var created = []; + + function create() { + var success = Clipboard.importEntities(BENCH_URL); + if (success === true) { + created = Clipboard.pasteEntities(spawnLocation) + print('created ' + created); + } + }; + + function cleanup() { + created.forEach(function(obj) { + Entities.deleteEntity(obj); + }) + }; + create(); this.cleanup = cleanup; } -HomeJunk = function(spawnLocation, spawnRotation) { - print('HOME CREATE JUNK') +Umbrella = function(spawnLocation, spawnRotation) { + print('HOME CREATE Umbrella'); var created = []; function create() { - var success = Clipboard.importEntities(JUNK_URL); + var success = Clipboard.importEntities(UMBRELLA_URL); if (success === true) { created = Clipboard.pasteEntities(spawnLocation) print('created ' + created); diff --git a/unpublishedScripts/DomainContent/Home/pingPongGun/target.js b/unpublishedScripts/DomainContent/Home/pingPongGun/target.js new file mode 100644 index 0000000000..1a8ea6a25b --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/pingPongGun/target.js @@ -0,0 +1,76 @@ +// +// Copyright 2016 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() { + + var _this = this; + _this.COLLISION_COOLDOWN_TIME = 5000; + + var startPosition = { + x: 1100.6343, + y: 460.5366, + z: -65.2142 + }; + + var startRotation = Quat.fromPitchYawRollDegrees(3.1471, -170.4121, -0.0060) + + _this.preload = function(entityID) { + + //set our id so other methods can get it. + _this.entityID = entityID; + + //variables we will use to keep track of when to reset the cow + _this.timeSinceLastCollision = 0; + _this.shouldUntip = true; + } + + _this.collisionWithEntity = function(myID, otherID, collisionInfo) { + //we dont actually use any of the parameters above, since we don't really care what we collided with, or the details of the collision. + //5 seconds after a collision, upright the target. protect from multiple collisions in a short timespan with the 'shouldUntip' variable + if (_this.shouldUntip) { + //in Hifi, preface setTimeout with Script.setTimeout + Script.setTimeout(function() { + _this.untip(); + _this.shouldUntip = true; + }, _this.COLLISION_COOLDOWN_TIME); + } + + _this.shouldUntip = false; + + } + + _this.untip = function() { + var props = Entities.getEntityProperties(this.entityID); + var rotation = Quat.safeEulerAngles(props.rotation) + if (rotation.x > 3 || rotation.x < -3 || rotation.z > 3 || rotation.z < -3) { + print('home target - too much pitch or roll, fix it'); + + //we zero out the velocity and angular velocity + Entities.editEntity(_this.entityID, { + position: startPosition, + rotation: startRotation, + velocity: { + x: 0, + y: 0, + z: 0 + }, + angularVelocity: { + x: 0, + y: 0, + z: 0 + } + }); + } + + + + } + + +}); \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/reset.js b/unpublishedScripts/DomainContent/Home/reset.js index f6e5a01c7c..cd1dcea76e 100644 --- a/unpublishedScripts/DomainContent/Home/reset.js +++ b/unpublishedScripts/DomainContent/Home/reset.js @@ -43,6 +43,8 @@ var transformerPath = Script.resolvePath("atp:/dressingRoom/wrapper.js"); + var blockyPath = Script.resolvePath("atp:/blocky/wrapper.js"); + Script.include(utilsPath); Script.include(kineticPath); @@ -53,6 +55,7 @@ Script.include(cuckooClockPath); Script.include(pingPongGunPath); Script.include(transformerPath); + Script.include(blockyPath); var TRANSFORMER_URL_ROBOT = 'atp:/dressingRoom/simple_robot.fbx'; @@ -60,7 +63,7 @@ var TRANSFORMER_URL_WILL = 'atp:/dressingRoom/will_T.fbx'; - var TRANSFORMER_URL_STYLIZED_FEMALE = 'atp:/dressingRoom/stylized_female.fbx'; + var TRANSFORMER_URL_STYLIZED_FEMALE = 'atp:/dressingRoom/ArtemisJacketOn.fbx'; var TRANSFORMER_URL_PRISCILLA = 'atp:/dressingRoom/priscilla.fbx'; @@ -199,6 +202,8 @@ _this.createKineticEntities(); _this.createScriptedEntities(); _this.setupDressingRoom(); + _this.createMilkPailBalls(); + _this.createTarget(); }, 750); } }, @@ -338,18 +343,18 @@ z: -0.0013 }); + var blocky = new BlockyGame({ + x: 1098.4424, + y: 460.3090, + z: -66.2190 + }) + print('HOME after creating scripted entities') }, createKineticEntities: function() { - var blocks = new Blocks({ - x: 1097.1383, - y: 460.3790, - z: -66.4895 - }); - var fruitBowl = new FruitBowl({ x: 1105.3185, y: 460.3221, @@ -380,12 +385,6 @@ z: -79.8097 }); - var blueChair = new BlueChair({ - x: 1100.4821, - y: 459.9147, - z: -75.9071 - }); - var stuffOnShelves = new StuffOnShelves({ x: 1105.9432, @@ -422,6 +421,25 @@ y: 461, z: -73.3 }); + + // var dressingRoomBricabrac = new Bricabrac({ + // x: 1106.8, + // y: 460.3909, + // z: -72.6 + // }); + + var bench = new Bench({ + x: 1100.1210, + y: 459.4552, + z: -75.4537 + }); + + var umbrella = new Umbrella({ + x: 1097.5510, + y: 459.5230, + z: -84.3897 + }); + print('HOME after creating kinetic entities'); }, @@ -510,6 +528,100 @@ var dais = Entities.addEntity(daisProperties); }, + createTarget: function() { + var targetProperties = { + type: 'Model', + modelURL: 'atp:/pingPongGun/Target.fbx', + shapeType: 'Compound', + compoundShapeURL: 'atp:/pingPongGun/Target.obj', + dimensions: { + x: 0.4937, + y: 0.6816, + z: 0.0778 + }, + rotation: Quat.fromPitchYawRollDegrees(3.1471, -170.4121, -0.0060), + gravity: { + x: 0, + y: -9.8, + z: 0 + }, + velocity: { + x: 0, + y: -0.1, + z: 0 + }, + position: { + x: 1100.6343, + y: 460.5366, + z: -65.2142 + }, + userData: JSON.stringify({ + grabbableKey: { + grabbable: true + }, + hifiHomeKey: { + reset: true + } + }), + script: 'atp:/pingPongGun/target.js', + density: 100, + dynamic: true + } + var target = Entities.addEntity(targetProperties); + }, + + createMilkPailBalls: function() { + var locations = [{ + x: 1099.0795, + y: 459.4186, + z: -70.8603 + }, { + x: 1099.2826, + y: 459.4186, + z: -70.9094 + }, { + x: 1099.5012, + y: 459.4186, + z: -71.1000 + }]; + + var ballProperties = { + type: 'Model', + modelURL: 'atp:/static_objects/StarBall.fbx', + shapeType: 'Sphere', + dimensions: { + x: 0.1646, + y: 0.1646, + z: 0.1646 + }, + gravity: { + x: 0, + y: -9.8, + z: 0 + }, + velocity: { + x: 0, + y: -0.1, + z: 0 + }, + userData: JSON.stringify({ + grabbableKey: { + grabbable: true + }, + hifiHomeKey: { + reset: true + } + }), + dynamic: true + }; + + locations.forEach(function(location) { + ballProperties.position = location; + var ball = Entities.addEntity(ballProperties); + }); + print('HOME made milk pail balls') + }, + createTransformers: function() { var firstDollPosition = { x: 1107.6, diff --git a/unpublishedScripts/DomainContent/Home/switches/dressingRoomDiscLights.js b/unpublishedScripts/DomainContent/Home/switches/dressingRoomLights.js similarity index 89% rename from unpublishedScripts/DomainContent/Home/switches/dressingRoomDiscLights.js rename to unpublishedScripts/DomainContent/Home/switches/dressingRoomLights.js index 6e2c4908b9..a44fa8f2f3 100644 --- a/unpublishedScripts/DomainContent/Home/switches/dressingRoomDiscLights.js +++ b/unpublishedScripts/DomainContent/Home/switches/dressingRoomLights.js @@ -147,6 +147,16 @@ setEntityCustomData('home-switch', _this.entityID, { state: 'on' }); + + Entities.editEntity(this.entityID, { + "animation": { + "currentFrame": 1, + "firstFrame": 1, + "hold": 1, + "lastFrame": 2, + "url": "atp:/switches/lightswitch.fbx" + }, + }); } else { glowLights.forEach(function(glowLight) { @@ -161,9 +171,18 @@ setEntityCustomData('home-switch', this.entityID, { state: 'off' }); + + Entities.editEntity(this.entityID, { + "animation": { + "currentFrame": 3, + "firstFrame": 3, + "hold": 1, + "lastFrame": 4, + "url": "atp:/switches/lightswitch.fbx" + }, + }); } - this.flipSwitch(); Audio.playSound(this.switchSound, { volume: 0.5, position: this.position @@ -171,20 +190,7 @@ }, - flipSwitch: function() { - var rotation = Entities.getEntityProperties(this.entityID, "rotation").rotation; - var axis = { - x: 0, - y: 1, - z: 0 - }; - var dQ = Quat.angleAxis(180, axis); - rotation = Quat.multiply(rotation, dQ); - Entities.editEntity(this.entityID, { - rotation: rotation - }); - }, preload: function(entityID) { this.entityID = entityID; diff --git a/unpublishedScripts/DomainContent/Home/switches/livingRoomFan.js b/unpublishedScripts/DomainContent/Home/switches/livingRoomFan.js index 6005503cf3..09d0fb97c8 100644 --- a/unpublishedScripts/DomainContent/Home/switches/livingRoomFan.js +++ b/unpublishedScripts/DomainContent/Home/switches/livingRoomFan.js @@ -11,7 +11,7 @@ (function() { - var FAN_SOUND_ENTITY_NAME ="home_sfx_ceiling_fan" + var FAN_SOUND_ENTITY_NAME = "home_sfx_ceiling_fan" var SEARCH_RADIUS = 100; var _this; var utilitiesScript = Script.resolvePath('../utils.js'); @@ -74,16 +74,16 @@ if (!_this.fanSoundEntity) { return; } - print('HOME FAN OFF 2') + print('HOME FAN OFF 2') var soundUserData = getEntityCustomData("soundKey", _this.fanSoundEntity); if (!soundUserData) { print("NO SOUND USER DATA! RETURNING."); return; } - print('HOME FAN OFF 3') + print('HOME FAN OFF 3') soundUserData.volume = 0.0; setEntityCustomData("soundKey", _this.fanSoundEntity, soundUserData); - print('HOME FAN OFF 4') + print('HOME FAN OFF 4') }, findFan: function() { @@ -105,7 +105,7 @@ var fan = null; print('HOME LOOKING FOR A FAN') - print('HOME TOTAL ENTITIES:: ' +entities.length) + print('HOME TOTAL ENTITIES:: ' + entities.length) entities.forEach(function(entity) { var props = Entities.getEntityProperties(entity); if (props.name === FAN_SOUND_ENTITY_NAME) { @@ -129,10 +129,22 @@ if (this._switch.state === 'off') { this.fanRotationOn(); this.fanSoundOn(); + setEntityCustomData('home-switch', this.entityID, { state: 'on' }); + Entities.editEntity(this.entityID, { + "animation": { + "currentFrame": 1, + "firstFrame": 1, + "hold": 1, + "lastFrame": 2, + "url": "atp:/switches/fanswitch.fbx" + }, + }) + + } else { this.fanRotationOff(); this.fanSoundOff(); @@ -140,9 +152,20 @@ setEntityCustomData('home-switch', this.entityID, { state: 'off' }); + Entities.editEntity(this.entityID, { + "animation": { + "currentFrame": 3, + "firstFrame": 3, + "hold": 1, + "lastFrame": 4, + "url": "atp:/switches/fanswitch.fbx" + }, + }) + + } - this.flipSwitch(); + Audio.playSound(this.switchSound, { volume: 0.5, position: this.position @@ -150,21 +173,6 @@ }, - flipSwitch: function() { - var rotation = Entities.getEntityProperties(this.entityID, "rotation").rotation; - var axis = { - x: 0, - y: 1, - z: 0 - }; - var dQ = Quat.angleAxis(180, axis); - rotation = Quat.multiply(rotation, dQ); - - Entities.editEntity(this.entityID, { - rotation: rotation - }); - }, - preload: function(entityID) { this.entityID = entityID; setEntityCustomData('grabbableKey', this.entityID, { diff --git a/unpublishedScripts/DomainContent/Home/switches/livingRoomDiscLights.js b/unpublishedScripts/DomainContent/Home/switches/livingRoomLights.js similarity index 89% rename from unpublishedScripts/DomainContent/Home/switches/livingRoomDiscLights.js rename to unpublishedScripts/DomainContent/Home/switches/livingRoomLights.js index eef64fd3f8..2c6208481f 100644 --- a/unpublishedScripts/DomainContent/Home/switches/livingRoomDiscLights.js +++ b/unpublishedScripts/DomainContent/Home/switches/livingRoomLights.js @@ -39,7 +39,7 @@ "Metal-brushed-light.jpg": "atp:/models/Lights-Living-Room-2.fbx/Lights-Living-Room-2.fbm/Metal-brushed-light.jpg", "Tex.CeilingLight.Emit": "atp:/models/Lights-Living-Room-2.fbx/Lights-Living-Room-2.fbm/CielingLight-On-Diffuse.jpg", "TexCeilingLight.Diffuse": "atp:/models/Lights-Living-Room-2.fbx/Lights-Living-Room-2.fbm/CielingLight-Base.jpg" - } + }; Entities.editEntity(glowDisc, { textures: JSON.stringify(data) @@ -51,7 +51,7 @@ "Metal-brushed-light.jpg": "atp:/models/Lights-Living-Room-2.fbx/Lights-Living-Room-2.fbm/Metal-brushed-light.jpg", "Tex.CeilingLight.Emit": "", "TexCeilingLight.Diffuse": "atp:/models/Lights-Living-Room-2.fbx/Lights-Living-Room-2.fbm/CielingLight-Base.jpg" - } + }; Entities.editEntity(glowDisc, { textures: JSON.stringify(data) @@ -143,6 +143,16 @@ state: 'on' }); + Entities.editEntity(this.entityID, { + "animation": { + "currentFrame": 1, + "firstFrame": 1, + "hold": 1, + "lastFrame": 2, + "url": "atp:/switches/lightswitch.fbx" + }, + }); + } else { glowLights.forEach(function(glowLight) { _this.glowLightOff(glowLight); @@ -156,9 +166,18 @@ setEntityCustomData('home-switch', this.entityID, { state: 'off' }); + + Entities.editEntity(this.entityID, { + "animation": { + "currentFrame": 3, + "firstFrame": 3, + "hold": 1, + "lastFrame": 4, + "url": "atp:/switches/lightswitch.fbx" + }, + }); } - this.flipSwitch(); Audio.playSound(this.switchSound, { volume: 0.5, position: this.position @@ -166,21 +185,6 @@ }, - flipSwitch: function() { - var rotation = Entities.getEntityProperties(this.entityID, "rotation").rotation; - var axis = { - x: 0, - y: 1, - z: 0 - }; - var dQ = Quat.angleAxis(180, axis); - rotation = Quat.multiply(rotation, dQ); - - Entities.editEntity(this.entityID, { - rotation: rotation - }); - }, - preload: function(entityID) { this.entityID = entityID; setEntityCustomData('grabbableKey', this.entityID, { diff --git a/unpublishedScripts/DomainContent/Home/teleport/downsparkle.json b/unpublishedScripts/DomainContent/Home/teleport/downsparkle.json new file mode 100644 index 0000000000..8682a4ed4c --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/teleport/downsparkle.json @@ -0,0 +1,65 @@ +down sparkle + +{ + "color": { + "red": "#", + "green": "c", + "blue": "f" + }, + "isEmitting": 1, + "maxParticles": 1000, + "lifespan": 2, + "emitRate": 10, + "emitSpeed": 0.1, + "speedSpread": 0.6, + "emitOrientation": { + "x": 0.8, + "y": 0, + "z": 0, + "w": 0.7071068286895752 + }, + "emitDimensions": { + "x": 0, + "y": 0, + "z": 0 + }, + "polarStart": 0, + "polarFinish": 0, + "azimuthStart": -3.1415927410125732, + "azimuthFinish": 3.1415927410125732, + "emitAcceleration": { + "x": 0, + "y": -1, + "z": 0 + }, + "accelerationSpread": { + "x": 0, + "y": 1, + "z": 0 + }, + "particleRadius": 0.02500000037252903, + "radiusSpread": 0, + "radiusStart": 0.079, + "radiusFinish": 0.053, + "colorSpread": { + "red": "#", + "green": "e", + "blue": "8" + }, + "colorStart": { + "red": 255, + "green": 255, + "blue": 255 + }, + "colorFinish": { + "red": "#", + "green": "d", + "blue": "4" + }, + "alpha": 1, + "alphaSpread": 0, + "alphaStart": 1, + "alphaFinish": 0, + "emitterShouldTrail": 1, + "textures": "atp:/teleport/sparkle1.png" +} \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/teleport/downsparkler.json b/unpublishedScripts/DomainContent/Home/teleport/downsparkler.json new file mode 100644 index 0000000000..09ec79910a --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/teleport/downsparkler.json @@ -0,0 +1,63 @@ +{ + "Entities": [ + { + "accelerationSpread": { + "x": 0, + "y": 1, + "z": 0 + }, + "color": { + "blue": 207, + "green": 207, + "red": 207 + }, + "colorFinish": { + "blue": 207, + "green": 207, + "red": 207 + }, + "colorSpread": { + "blue": 125, + "green": 125, + "red": 232 + }, + "colorStart": { + "blue": 207, + "green": 207, + "red": 207 + }, + "created": "2016-05-23T20:41:38Z", + "dimensions": { + "x": 2.6566545963287354, + "y": 2.6566545963287354, + "z": 2.6566545963287354 + }, + "emitAcceleration": { + "x": 0, + "y": -1, + "z": 0 + }, + "emitOrientation": { + "w": 0.66226339340209961, + "x": 0.74927115440368652, + "y": -1.5258940038620494e-05, + "z": -1.5258940038620494e-05 + }, + "emitRate": 10, + "emitSpeed": 0.10000000149011612, + "id": "{e700e0a1-026a-4ebd-8609-6068b32df14e}", + "lifespan": 2, + "name": "home_particle_teleport_sparkle_down", + "queryAACube": { + "scale": 4.6014609336853027, + "x": -2.3007304668426514, + "y": -2.3007304668426514, + "z": -2.3007304668426514 + }, + "speedSpread": 0.60000002384185791, + "textures": "atp:/teleport/sparkle1.png", + "type": "ParticleEffect" + } + ], + "Version": 57 +} diff --git a/unpublishedScripts/DomainContent/Home/teleport/upsparkle.json b/unpublishedScripts/DomainContent/Home/teleport/upsparkle.json new file mode 100644 index 0000000000..508366d293 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/teleport/upsparkle.json @@ -0,0 +1,63 @@ +{ + "color": { + "red": 255, + "green": 255, + "blue": 255 + }, + "isEmitting": 1, + "maxParticles": 210, + "lifespan": 3.6, + "emitRate": 11, + "emitSpeed": 0.5, + "speedSpread": 0.8, + "emitOrientation": { + "x": -1, + "y": -0.0000152587890625, + "z": -0.0000152587890625, + "w": 1 + }, + "emitDimensions": { + "x": 0, + "y": 0, + "z": 0 + }, + "polarStart": 0, + "polarFinish": 0, + "azimuthStart": -3.1415927410125732, + "azimuthFinish": 3.1415927410125732, + "emitAcceleration": { + "x": 0, + "y": 0.2, + "z": 0 + }, + "accelerationSpread": { + "x": 0, + "y": 0.30000000000000004, + "z": 0 + }, + "particleRadius": 0.02500000037252903, + "radiusSpread": 0, + "radiusStart": 0.013, + "radiusFinish": 0.014, + "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": 1, + "alphaFinish": 0, + "emitterShouldTrail": 0, + "textures": "atp:/teleport/sparkle2.png" +} \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/teleport/upsparkler.json b/unpublishedScripts/DomainContent/Home/teleport/upsparkler.json new file mode 100644 index 0000000000..a573b3deae --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/teleport/upsparkler.json @@ -0,0 +1,44 @@ +{ + "Entities": [ + { + "accelerationSpread": { + "x": 0, + "y": 0.30000001192092896, + "z": 0 + }, + "created": "2016-05-23T20:56:55Z", + "dimensions": { + "x": 15.000479698181152, + "y": 15.000479698181152, + "z": 15.000479698181152 + }, + "emitAcceleration": { + "x": 0, + "y": 0.20000000298023224, + "z": 0 + }, + "emitOrientation": { + "w": 0.7070763111114502, + "x": -0.70713728666305542, + "y": -1.5258539860951714e-05, + "z": -1.5258539860951714e-05 + }, + "emitRate": 11, + "emitSpeed": 0.5, + "id": "{57104b1c-01a9-4f2b-b7bf-9eb406e8d78b}", + "lifespan": 3.5999999046325684, + "maxParticles": 210, + "name": "home_teleport_sparkler_up", + "queryAACube": { + "scale": 25.981592178344727, + "x": -12.990796089172363, + "y": -12.990796089172363, + "z": -12.990796089172363 + }, + "speedSpread": 0.80000001192092896, + "textures": "atp:/teleport/sparkle2.png", + "type": "ParticleEffect" + } + ], + "Version": 57 +} diff --git a/unpublishedScripts/DomainContent/Toybox/bow/bow.js b/unpublishedScripts/DomainContent/Toybox/bow/bow.js index b287966d2c..0c16bcbc7b 100644 --- a/unpublishedScripts/DomainContent/Toybox/bow/bow.js +++ b/unpublishedScripts/DomainContent/Toybox/bow/bow.js @@ -125,18 +125,32 @@ Entities.deleteEntity(this.arrow); }, + startNearGrab: function(entityID, args) { + _this.startEquip(entityID, args); + }, + + continueNearGrab: function(entityID, args) { + _this.continueEquip(entityID, args); + }, + + releaseGrab: function(entityID, args) { + _this.releaseEquip(entityID, args); + }, + startEquip: function(entityID, args) { this.hand = args[0]; avatarID = args[1]; //disable the opposite hand in handControllerGrab.js by message var handToDisable = this.hand === 'right' ? 'left' : 'right'; - print("disabling hand: " + handToDisable); Messages.sendMessage('Hifi-Hand-Disabler', handToDisable); var data = getEntityCustomData('grabbableKey', this.entityID, {}); data.grabbable = false; setEntityCustomData('grabbableKey', this.entityID, data); + Entities.editEntity(_this.entityID, { + collidesWith: "" + }) }, continueEquip: function(entityID, args) { @@ -169,7 +183,6 @@ }, releaseEquip: function(entityID, args) { - // print('RELEASE GRAB EVENT') Messages.sendMessage('Hifi-Hand-Disabler', "none") this.stringDrawn = false; @@ -181,10 +194,12 @@ Entities.deleteEntity(this.arrow); this.aiming = false; this.hasArrowNotched = false; + Entities.editEntity(_this.entityID, { + collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar" + }) }, createArrow: function() { - print('create arrow') this.playArrowNotchSound(); var arrow = Entities.addEntity({ @@ -209,25 +224,24 @@ var makeArrowStick = function(entityA, entityB, collision) { Entities.editEntity(entityA, { - angularVelocity: { - x: 0, - y: 0, - z: 0 - }, - velocity: { - x: 0, - y: 0, - z: 0 - }, - gravity: { - x: 0, - y: 0, - z: 0 - }, - position: collision.contactPoint, - dynamic: false - }) - // print('ARROW COLLIDED WITH::' + entityB); + angularVelocity: { + x: 0, + y: 0, + z: 0 + }, + velocity: { + x: 0, + y: 0, + z: 0 + }, + gravity: { + x: 0, + y: 0, + z: 0 + }, + position: collision.contactPoint, + dynamic: false + }) Script.removeEventHandler(arrow, "collisionWithEntity", makeArrowStick) } @@ -283,7 +297,6 @@ }, updateStringPositions: function() { - // print('update string positions!!!') var upVector = Quat.getUp(this.bowProperties.rotation); var upOffset = Vec3.multiply(upVector, TOP_NOTCH_OFFSET); var downVector = Vec3.multiply(-1, Quat.getUp(this.bowProperties.rotation)); @@ -354,7 +367,6 @@ if (this.triggerValue < DRAW_STRING_THRESHOLD && this.stringDrawn === true) { // firing the arrow - // print('HIT RELEASE LOOP IN CHECK'); this.drawStrings(); this.hasArrowNotched = false; @@ -364,7 +376,6 @@ } else if (this.triggerValue > DRAW_STRING_THRESHOLD && this.stringDrawn === true) { - // print('HIT CONTINUE LOOP IN CHECK') //continuing to aim the arrow this.aiming = true; @@ -372,7 +383,6 @@ this.updateArrowPositionInNotch(); } else if (this.triggerValue > DRAW_STRING_THRESHOLD && this.stringDrawn === false) { - // print('HIT START LOOP IN CHECK'); this.arrow = this.createArrow(); this.playStringPullSound(); @@ -461,10 +471,14 @@ // rotation: arrowProps.rotation }; + //actually shoot the arrow and play its sound Entities.editEntity(this.arrow, arrowProperties); this.playShootArrowSound(); + var backHand = this.hand === 'left' ? 1 : 0; + var haptic = Controller.triggerShortHapticPulse(1, backHand); + //clear the strings back to only the single straight one this.deleteStrings(); Entities.editEntity(this.preNotchString, { @@ -526,4 +540,4 @@ }; return new Bow(); -}); +}); \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Toybox/bow/createBow.js b/unpublishedScripts/DomainContent/Toybox/bow/createBow.js index 5deec3f6bc..f1ed9eb263 100644 --- a/unpublishedScripts/DomainContent/Toybox/bow/createBow.js +++ b/unpublishedScripts/DomainContent/Toybox/bow/createBow.js @@ -62,24 +62,35 @@ function makeBow() { shapeType: 'compound', compoundShapeURL: COLLISION_HULL_URL, script: SCRIPT_URL, + collidesWith: 'dynamic,kinematic,static', userData: JSON.stringify({ grabbableKey: { invertSolidWhileHeld: true }, - wearable:{joints:{RightHand:[{x:0.03960523009300232, - y:0.01979270577430725, - z:0.03294898942112923}, - {x:-0.7257906794548035, - y:-0.4611682891845703, - z:0.4436084032058716, - w:-0.25251442193984985}], - LeftHand:[{x:0.0055799782276153564, - y:0.04354757443070412, - z:0.05119767785072327}, - {x:-0.14914104342460632, - y:0.6448180079460144, - z:-0.2888556718826294, - w:-0.6917579770088196}]}} + wearable: { + joints: { + RightHand: [{ + x: 0.03960523009300232, + y: 0.01979270577430725, + z: 0.03294898942112923 + }, { + x: -0.7257906794548035, + y: -0.4611682891845703, + z: 0.4436084032058716, + w: -0.25251442193984985 + }], + LeftHand: [{ + x: 0.0055799782276153564, + y: 0.04354757443070412, + z: 0.05119767785072327 + }, { + x: -0.14914104342460632, + y: 0.6448180079460144, + z: -0.2888556718826294, + w: -0.6917579770088196 + }] + } + } }) }; @@ -148,4 +159,4 @@ function cleanup() { Entities.deleteEntity(preNotchString); } -Script.scriptEnding.connect(cleanup); +Script.scriptEnding.connect(cleanup); \ No newline at end of file