diff --git a/interface/resources/images/loadingBar_placard.png b/interface/resources/images/loadingBar_placard.png new file mode 100644 index 0000000000..9fd4c9e71f Binary files /dev/null and b/interface/resources/images/loadingBar_placard.png differ diff --git a/interface/resources/images/loadingBar_progress.png b/interface/resources/images/loadingBar_progress.png new file mode 100644 index 0000000000..2b1fb089d9 Binary files /dev/null and b/interface/resources/images/loadingBar_progress.png differ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9405915bcf..b4a8c43be7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1387,6 +1387,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + connect(this, &Application::interstitialModeChanged, audioIO, &AudioClient::setInterstitialStatus); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -1645,7 +1646,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo audioClient->setMuted(!audioClient->isMuted()); } else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) { cycleCamera(); - } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { + } else if (action == controller::toInt(controller::Action::CONTEXT_MENU) && !isInterstitialMode()) { toggleTabletUI(); } else if (action == controller::toInt(controller::Action::RETICLE_X)) { auto oldPos = getApplicationCompositor().getReticlePosition(); @@ -2294,6 +2295,25 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Preload Tablet sounds DependencyManager::get()->preloadSounds(); + connect(this, &Application::interstitialModeChanged, this, [this] (bool interstitialMode) { + if (!interstitialMode) { + DependencyManager::get()->negotiateAudioFormat(); + _queryExpiry = SteadyClock::now(); + if (_avatarOverrideUrl.isValid()) { + getMyAvatar()->useFullAvatarURL(_avatarOverrideUrl); + } + + if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->getSkeletonModelURL()) { + getMyAvatar()->resetFullAvatarURL(); + } + getMyAvatar()->markIdentityDataChanged(); + getMyAvatar()->resetLastSent(); + + // transmit a "sendAll" packet to the AvatarMixer we just connected to. + getMyAvatar()->sendAvatarDataPacket(true); + } + }); + _pendingIdleEvent = false; _pendingRenderEvent = false; @@ -2994,6 +3014,9 @@ void Application::initializeUi() { if (_window && _window->isFullScreen()) { setFullscreen(nullptr, true); } + + + setIsInterstitialMode(true); } @@ -3470,6 +3493,22 @@ bool Application::isServerlessMode() const { return false; } +bool Application::isInterstitialMode() const { + bool interstitialModeEnabled = Menu::getInstance()->isOptionChecked("Enable Interstitial"); + return interstitialModeEnabled ? _interstitialMode : false; +} + +void Application::setIsInterstitialMode(bool interstitialMode) { + auto menu = Menu::getInstance(); + bool interstitialModeEnabled = menu->isOptionChecked("Enable Interstitial"); + if (_interstitialMode != interstitialMode && interstitialModeEnabled) { + _interstitialMode = interstitialMode; + + DependencyManager::get()->setPinned(_interstitialMode); + emit interstitialModeChanged(_interstitialMode); + } +} + void Application::setIsServerlessMode(bool serverlessDomain) { auto tree = getEntities()->getTree(); if (tree) { @@ -3750,7 +3789,7 @@ void Application::keyPressEvent(QKeyEvent* event) { _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface->isKeyCaptured(event)) { + if (_controllerScriptingInterface->isKeyCaptured(event) || isInterstitialMode()) { return; } @@ -5308,6 +5347,7 @@ void Application::resetPhysicsReadyInformation() { _fullSceneCounterAtLastPhysicsCheck = 0; _nearbyEntitiesCountAtLastPhysicsCheck = 0; _nearbyEntitiesStabilityCount = 0; + _nearbyEntitiesReadyCount = 0; _physicsEnabled = false; _octreeProcessor.startEntitySequence(); } @@ -5535,6 +5575,7 @@ void Application::update(float deltaTime) { return; } + if (!_physicsEnabled) { if (!domainLoadingInProgress) { PROFILE_ASYNC_BEGIN(app, "Scene Loading", ""); @@ -5544,7 +5585,8 @@ void Application::update(float deltaTime) { // we haven't yet enabled physics. we wait until we think we have all the collision information // for nearby entities before starting bullet up. quint64 now = usecTimestampNow(); - if (isServerlessMode() || _octreeProcessor.isLoadSequenceComplete()) { + bool renderReady = _octreeProcessor.isEntitiesRenderReady(); + if (isServerlessMode() || (_octreeProcessor.isLoadSequenceComplete() && renderReady)) { // we've received a new full-scene octree stats packet, or it's been long enough to try again anyway _lastPhysicsCheckTime = now; _fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter; @@ -5555,6 +5597,7 @@ void Application::update(float deltaTime) { // scene is ready to compute its collision shape. if (getMyAvatar()->isReadyForPhysics()) { _physicsEnabled = true; + setIsInterstitialMode(false); getMyAvatar()->updateMotionBehaviorFromMenu(); } } @@ -5634,7 +5677,7 @@ void Application::update(float deltaTime) { // Transfer the user inputs to the driveKeys // FIXME can we drop drive keys and just have the avatar read the action states directly? myAvatar->clearDriveKeys(); - if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { + if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT && !isInterstitialMode()) { if (!_controllerScriptingInterface->areActionsCaptured() && _myCamera.getMode() != CAMERA_MODE_MIRROR) { myAvatar->setDriveKey(MyAvatar::TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z)); myAvatar->setDriveKey(MyAvatar::TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y)); @@ -5952,7 +5995,7 @@ void Application::update(float deltaTime) { // send packet containing downstream audio stats to the AudioMixer { quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; - if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { + if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS && !isInterstitialMode()) { _lastSendDownstreamAudioStats = now; QMetaObject::invokeMethod(DependencyManager::get().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); @@ -6115,21 +6158,23 @@ void Application::updateRenderArgs(float deltaTime) { } void Application::queryAvatars() { - auto avatarPacket = NLPacket::create(PacketType::AvatarQuery); - auto destinationBuffer = reinterpret_cast(avatarPacket->getPayload()); - unsigned char* bufferStart = destinationBuffer; + if (!isInterstitialMode()) { + auto avatarPacket = NLPacket::create(PacketType::AvatarQuery); + auto destinationBuffer = reinterpret_cast(avatarPacket->getPayload()); + unsigned char* bufferStart = destinationBuffer; - uint8_t numFrustums = (uint8_t)_conicalViews.size(); - memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums)); - destinationBuffer += sizeof(numFrustums); + uint8_t numFrustums = (uint8_t)_conicalViews.size(); + memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums)); + destinationBuffer += sizeof(numFrustums); - for (const auto& view : _conicalViews) { - destinationBuffer += view.serialize(destinationBuffer); + for (const auto& view : _conicalViews) { + destinationBuffer += view.serialize(destinationBuffer); + } + + avatarPacket->setPayloadSize(destinationBuffer - bufferStart); + + DependencyManager::get()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); } - - avatarPacket->setPayloadSize(destinationBuffer - bufferStart); - - DependencyManager::get()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); } @@ -6352,6 +6397,7 @@ void Application::clearDomainOctreeDetails() { qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); + setIsInterstitialMode(true); _octreeServerSceneStats.withWriteLock([&] { _octreeServerSceneStats.clear(); @@ -6437,11 +6483,11 @@ void Application::nodeActivated(SharedNodePointer node) { _octreeQuery.incrementConnectionID(); } - if (node->getType() == NodeType::AudioMixer) { + if (node->getType() == NodeType::AudioMixer && !isInterstitialMode()) { DependencyManager::get()->negotiateAudioFormat(); } - if (node->getType() == NodeType::AvatarMixer) { + if (node->getType() == NodeType::AvatarMixer && !isInterstitialMode()) { _queryExpiry = SteadyClock::now(); // new avatar mixer, send off our identity packet on next update loop @@ -6558,6 +6604,7 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { _nearbyEntitiesCountAtLastPhysicsCheck = nearbyCount; const uint32_t MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT = 3; + uint32_t readyNearbyEntities = 0; if (_nearbyEntitiesStabilityCount >= MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT) { // We've seen the same number of nearby entities for several stats packets in a row. assume we've got all // the local entities. @@ -6567,8 +6614,11 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { HIFI_FCDEBUG(interfaceapp(), "Physics disabled until entity loads: " << entity->getID() << entity->getName()); // don't break here because we want all the relevant entities to start their downloads result = false; + } else { + readyNearbyEntities++; } } + _nearbyEntitiesReadyCount = readyNearbyEntities; return result; } return false; @@ -7809,7 +7859,7 @@ float Application::getRenderResolutionScale() const { } void Application::notifyPacketVersionMismatch() { - if (!_notifiedPacketVersionMismatchThisDomain) { + if (!_notifiedPacketVersionMismatchThisDomain && !isInterstitialMode()) { _notifiedPacketVersionMismatchThisDomain = true; QString message = "The location you are visiting is running an incompatible server version.\n"; diff --git a/interface/src/Application.h b/interface/src/Application.h index 7fe88e9b6a..35f998a04d 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -224,11 +224,17 @@ public: void setHmdTabletBecomesToolbarSetting(bool value); bool getPreferStylusOverLaser() { return _preferStylusOverLaserSetting.get(); } void setPreferStylusOverLaser(bool value); + + uint32_t getEntitiesStabilityCount() { return _nearbyEntitiesStabilityCount; } + uint32_t getNearbyEntitiesReadyCount() { return _nearbyEntitiesReadyCount; } + uint32_t getNearbyEntitiesCount() { return _nearbyEntitiesCountAtLastPhysicsCheck; } // FIXME: Remove setting completely or make available through JavaScript API? //bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); } bool getPreferAvatarFingerOverStylus() { return false; } void setPreferAvatarFingerOverStylus(bool value); + float getDomainLoadingProgress() { return _octreeProcessor.domainLoadingProgress(); } + float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); } void setSettingConstrainToolbarPosition(bool setting); @@ -304,6 +310,7 @@ public: void saveNextPhysicsStats(QString filename); bool isServerlessMode() const; + bool isInterstitialMode() const; void replaceDomainContent(const QString& url); @@ -330,6 +337,7 @@ signals: void activeDisplayPluginChanged(); void uploadRequest(QString path); + void interstitialModeChanged(bool interstitialMode); public slots: QVector pasteEntities(float x, float y, float z); @@ -427,6 +435,8 @@ public slots: void setIsServerlessMode(bool serverlessDomain); void loadServerlessDomain(QUrl domainURL, bool errorDomain = false); + void setIsInterstitialMode(bool interstialMode); + void loadServerlessDomain(QUrl domainURL); void updateVerboseLogging(); @@ -627,6 +637,7 @@ private: QHash _keysPressed; bool _enableProcessOctreeThread; + bool _interstitialMode { false }; OctreePacketProcessor _octreeProcessor; EntityEditPacketSender _entityEditSender; @@ -722,6 +733,7 @@ private: uint32_t _fullSceneCounterAtLastPhysicsCheck { 0 }; // _fullSceneReceivedCounter last time we checked physics ready uint32_t _nearbyEntitiesCountAtLastPhysicsCheck { 0 }; // how many in-range entities last time we checked physics ready uint32_t _nearbyEntitiesStabilityCount { 0 }; // how many times has _nearbyEntitiesCountAtLastPhysicsCheck been the same + uint32_t _nearbyEntitiesReadyCount { 0 }; quint64 _lastPhysicsCheckTime { usecTimestampNow() }; // when did we last check to see if physics was ready bool _keyboardDeviceHasFocus { true }; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a6ba983ab5..15898a73b1 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -787,6 +787,7 @@ Menu::Menu() { // Developer > Show Overlays addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Overlays, 0, true); + addCheckableActionToQMenuAndActionHash(developerMenu, "Enable Interstitial", 0, false); #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 9a7d8ef0c8..f579b51d0d 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -137,7 +137,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS) { + if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !qApp->isInterstitialMode()) { // send head/hand data to the avatar mixer and voxel server PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); @@ -808,13 +808,13 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV QString currentSessionUUID = avatar->getSessionUUID().toString(); if (specificAvatarIdentifiers.isEmpty() || specificAvatarIdentifiers.contains(currentSessionUUID)) { QJsonObject thisAvatarPalData; - + auto myAvatar = DependencyManager::get()->getMyAvatar(); if (currentSessionUUID == myAvatar->getSessionUUID().toString()) { currentSessionUUID = ""; } - + thisAvatarPalData.insert("sessionUUID", currentSessionUUID); thisAvatarPalData.insert("sessionDisplayName", avatar->getSessionDisplayName()); thisAvatarPalData.insert("audioLoudness", avatar->getAudioLoudness()); diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 4bc6817a9e..e670153a77 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -137,3 +137,11 @@ void OctreePacketProcessor::startEntitySequence() { bool OctreePacketProcessor::isLoadSequenceComplete() const { return _safeLanding->isLoadSequenceComplete(); } + +bool OctreePacketProcessor::isEntitiesRenderReady() const { + return _safeLanding->entitiesRenderReady(); +} + +float OctreePacketProcessor::domainLoadingProgress() { + return _safeLanding->loadingProgressPercentage(); +} diff --git a/interface/src/octree/OctreePacketProcessor.h b/interface/src/octree/OctreePacketProcessor.h index f9c24ddc51..71e22bf240 100644 --- a/interface/src/octree/OctreePacketProcessor.h +++ b/interface/src/octree/OctreePacketProcessor.h @@ -27,6 +27,8 @@ public: void startEntitySequence(); bool isLoadSequenceComplete() const; + bool isEntitiesRenderReady() const; + float domainLoadingProgress(); signals: void packetVersionMismatch(); diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 60b660f66a..6b7a0f582c 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -13,6 +13,7 @@ #include "EntityTreeRenderer.h" #include "ModelEntityItem.h" #include "InterfaceLogging.h" +#include "Application.h" const int SafeLanding::SEQUENCE_MODULO = std::numeric_limits::max() + 1; @@ -36,6 +37,7 @@ void SafeLanding::startEntitySequence(QSharedPointer entityT if (entityTree) { Locker lock(_lock); _entityTree = entityTree; + _trackedEntitiesRenderStatus.clear(); _trackedEntities.clear(); _trackingEntities = true; connect(std::const_pointer_cast(_entityTree).get(), @@ -53,6 +55,7 @@ void SafeLanding::startEntitySequence(QSharedPointer entityT void SafeLanding::stopEntitySequence() { Locker lock(_lock); _trackingEntities = false; + _maxTrackedEntityCount = 0; _initialStart = INVALID_SEQUENCE; _initialEnd = INVALID_SEQUENCE; _trackedEntities.clear(); @@ -79,12 +82,20 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { } } } + + _trackedEntitiesRenderStatus.emplace(entityID, entity); + float trackedEntityCount = (float)_trackedEntitiesRenderStatus.size(); + + if (trackedEntityCount > _maxTrackedEntityCount) { + _maxTrackedEntityCount = trackedEntityCount; + } } } void SafeLanding::deleteTrackedEntity(const EntityItemID& entityID) { Locker lock(_lock); _trackedEntities.erase(entityID); + _trackedEntitiesRenderStatus.erase(entityID); } void SafeLanding::setCompletionSequenceNumbers(int first, int last) { @@ -116,6 +127,16 @@ bool SafeLanding::isLoadSequenceComplete() { return !_trackingEntities; } +float SafeLanding::loadingProgressPercentage() { + Locker lock(_lock); + if (_maxTrackedEntityCount > 0) { + float trackedEntityCount = (float)_trackedEntitiesRenderStatus.size(); + return ((_maxTrackedEntityCount - trackedEntityCount) / _maxTrackedEntityCount); + } + + return 0.0f; +} + bool SafeLanding::isSequenceNumbersComplete() { if (_initialStart != INVALID_SEQUENCE) { Locker lock(_lock); @@ -148,6 +169,24 @@ bool SafeLanding::isEntityPhysicsComplete() { return _trackedEntities.empty(); } +bool SafeLanding::entitiesRenderReady() { + Locker lock(_lock); + auto entityTree = qApp->getEntities(); + for (auto entityMapIter = _trackedEntitiesRenderStatus.begin(); entityMapIter != _trackedEntitiesRenderStatus.end(); ++entityMapIter) { + auto entity = entityMapIter->second; + bool visuallyReady = entity->isVisuallyReady(); + if (visuallyReady || !entityTree->renderableForEntityId(entityMapIter->first)) { + entityMapIter = _trackedEntitiesRenderStatus.erase(entityMapIter); + if (entityMapIter == _trackedEntitiesRenderStatus.end()) { + break; + } + } else { + entity->requestRenderUpdate(); + } + } + return _trackedEntitiesRenderStatus.empty(); +} + float SafeLanding::ElevatedPriority(const EntityItem& entityItem) { return entityItem.getCollisionless() ? 0.0f : 10.0f; } diff --git a/interface/src/octree/SafeLanding.h b/interface/src/octree/SafeLanding.h index 9177930d81..611b75ab79 100644 --- a/interface/src/octree/SafeLanding.h +++ b/interface/src/octree/SafeLanding.h @@ -18,6 +18,7 @@ #include #include "EntityItem.h" +#include "EntityDynamicInterface.h" class EntityTreeRenderer; class EntityItemID; @@ -29,6 +30,8 @@ public: void setCompletionSequenceNumbers(int first, int last); // 'last' exclusive. void noteReceivedsequenceNumber(int sequenceNumber); bool isLoadSequenceComplete(); + bool entitiesRenderReady(); + float loadingProgressPercentage(); private slots: void addTrackedEntity(const EntityItemID& entityID); @@ -45,10 +48,12 @@ private: EntityTreePointer _entityTree; using EntityMap = std::map; EntityMap _trackedEntities; + EntityMap _trackedEntitiesRenderStatus; static constexpr int INVALID_SEQUENCE = -1; int _initialStart { INVALID_SEQUENCE }; int _initialEnd { INVALID_SEQUENCE }; + float _maxTrackedEntityCount { 0.0f }; struct SequenceLessThan { bool operator()(const int& a, const int& b) const; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index ba86925581..9e13e2affb 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -584,3 +584,8 @@ void WindowScriptingInterface::onMessageBoxSelected(int button) { _messageBoxes.remove(id); } } + + +float WindowScriptingInterface::domainLoadingProgress() { + return qApp->getDomainLoadingProgress(); +} diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 77895e0e76..626d142785 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -561,6 +561,8 @@ public slots: */ void closeMessageBox(int id); + float domainLoadingProgress(); + private slots: void onWindowGeometryChanged(const QRect& geometry); void onMessageBoxSelected(int button); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 8434545f22..de73ac0b2f 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -106,6 +106,10 @@ extern std::atomic DECIMATED_TEXTURE_COUNT; extern std::atomic RECTIFIED_TEXTURE_COUNT; void Stats::updateStats(bool force) { + + if (qApp->isInterstitialMode()) { + return; + } QQuickItem* parent = parentItem(); if (!force) { if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6a9363f309..7af8a05f25 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -651,7 +651,6 @@ void AudioClient::stop() { } void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - char bitset; message->readPrimitive(&bitset); @@ -664,11 +663,10 @@ void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - if (message->getType() == PacketType::SilentAudioFrame) { _silentInbound.increment(); } else { @@ -1026,80 +1024,82 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { } void AudioClient::handleAudioInput(QByteArray& audioBuffer) { - if (_muted) { - _lastInputLoudness = 0.0f; - _timeSinceLastClip = 0.0f; - } else { - int16_t* samples = reinterpret_cast(audioBuffer.data()); - int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; - int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); - - if (_isNoiseGateEnabled) { - // The audio gate includes DC removal - _audioGate->render(samples, samples, numFrames); - } else { - _audioGate->removeDC(samples, samples, numFrames); - } - - int32_t loudness = 0; - assert(numSamples < 65536); // int32_t loudness cannot overflow - bool didClip = false; - for (int i = 0; i < numSamples; ++i) { - const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); - int32_t sample = std::abs((int32_t)samples[i]); - loudness += sample; - didClip |= (sample > CLIPPING_THRESHOLD); - } - _lastInputLoudness = (float)loudness / numSamples; - - if (didClip) { + if (!_interstitialMode) { + if (_muted) { + _lastInputLoudness = 0.0f; _timeSinceLastClip = 0.0f; - } else if (_timeSinceLastClip >= 0.0f) { - _timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE; + } else { + int16_t* samples = reinterpret_cast(audioBuffer.data()); + int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; + int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); + + if (_isNoiseGateEnabled) { + // The audio gate includes DC removal + _audioGate->render(samples, samples, numFrames); + } else { + _audioGate->removeDC(samples, samples, numFrames); + } + + int32_t loudness = 0; + assert(numSamples < 65536); // int32_t loudness cannot overflow + bool didClip = false; + for (int i = 0; i < numSamples; ++i) { + const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); + int32_t sample = std::abs((int32_t)samples[i]); + loudness += sample; + didClip |= (sample > CLIPPING_THRESHOLD); + } + _lastInputLoudness = (float)loudness / numSamples; + + if (didClip) { + _timeSinceLastClip = 0.0f; + } else if (_timeSinceLastClip >= 0.0f) { + _timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE; + } + + emit inputReceived(audioBuffer); } - emit inputReceived(audioBuffer); + emit inputLoudnessChanged(_lastInputLoudness); + + // state machine to detect gate opening and closing + bool audioGateOpen = (_lastInputLoudness != 0.0f); + bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened + bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed + _audioGateOpen = audioGateOpen; + + if (openedInLastBlock) { + emit noiseGateOpened(); + } else if (closedInLastBlock) { + emit noiseGateClosed(); + } + + // the codec must be flushed to silence before sending silent packets, + // so delay the transition to silent packets by one packet after becoming silent. + auto packetType = _shouldEchoToServer ? PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho; + if (!audioGateOpen && !closedInLastBlock) { + packetType = PacketType::SilentAudioFrame; + _silentOutbound.increment(); + } else { + _audioOutbound.increment(); + } + + Transform audioTransform; + audioTransform.setTranslation(_positionGetter()); + audioTransform.setRotation(_orientationGetter()); + + QByteArray encodedBuffer; + if (_encoder) { + _encoder->encode(audioBuffer, encodedBuffer); + } else { + encodedBuffer = audioBuffer; + } + + emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, + audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, + packetType, _selectedCodecName); + _stats.sentPacket(); } - - emit inputLoudnessChanged(_lastInputLoudness); - - // state machine to detect gate opening and closing - bool audioGateOpen = (_lastInputLoudness != 0.0f); - bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened - bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed - _audioGateOpen = audioGateOpen; - - if (openedInLastBlock) { - emit noiseGateOpened(); - } else if (closedInLastBlock) { - emit noiseGateClosed(); - } - - // the codec must be flushed to silence before sending silent packets, - // so delay the transition to silent packets by one packet after becoming silent. - auto packetType = _shouldEchoToServer ? PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho; - if (!audioGateOpen && !closedInLastBlock) { - packetType = PacketType::SilentAudioFrame; - _silentOutbound.increment(); - } else { - _audioOutbound.increment(); - } - - Transform audioTransform; - audioTransform.setTranslation(_positionGetter()); - audioTransform.setRotation(_orientationGetter()); - - QByteArray encodedBuffer; - if (_encoder) { - _encoder->encode(audioBuffer, encodedBuffer); - } else { - encodedBuffer = audioBuffer; - } - - emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, - audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, - packetType, _selectedCodecName); - _stats.sentPacket(); } void AudioClient::handleMicAudioInput() { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 8599c990a3..90860798b3 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -187,6 +187,7 @@ public slots: void handleRecordedAudioInput(const QByteArray& audio); void reset(); void audioMixerKilled(); + void setInterstitialStatus(bool interstitialMode) { _interstitialMode = interstitialMode; } void setMuted(bool muted, bool emitSignal = true); bool isMuted() { return _muted; } @@ -416,6 +417,7 @@ private: QVector _activeLocalAudioInjectors; bool _isPlayingBackRecording { false }; + bool _interstitialMode { true }; CodecPluginPointer _codec; QString _selectedCodecName; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 34936c2c48..8be5b172ce 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1297,9 +1297,20 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } }); - // Check for removal ModelPointer model; withReadLock([&] { model = _model; }); + + withWriteLock([&] { + bool visuallyReady = true; + if (_hasModel) { + if (model && _didLastVisualGeometryRequestSucceed) { + visuallyReady = (_prevModelLoaded && _texturesLoaded); + } + } + entity->setVisuallyReady(visuallyReady); + }); + + // Check for removal if (!_hasModel) { if (model) { model->removeFromScene(scene, transaction); @@ -1441,11 +1452,11 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // That is where _currentFrame and _lastAnimated were updated. if (_animating) { DETAILED_PROFILE_RANGE(simulation_physics, "Animate"); - + if (!jointsMapped()) { mapJoints(entity, model->getJointNames()); //else the joint have been mapped before but we have a new animation to load - } else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { + } else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { _animation = DependencyManager::get()->getAnimation(entity->getAnimationURL()); _jointMappingCompleted = false; mapJoints(entity, model->getJointNames()); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 9430e97e54..cf452c9cf7 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -288,6 +288,17 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen updateHazeFromEntity(entity); } + + bool visuallyReady = true; + uint32_t skyboxMode = entity->getSkyboxMode(); + if (skyboxMode == COMPONENT_MODE_ENABLED && !_skyboxTextureURL.isEmpty()) { + bool skyboxLoadedOrFailed = (_skyboxTexture && (_skyboxTexture->isLoaded() || _skyboxTexture->isFailed())); + + visuallyReady = skyboxLoadedOrFailed; + } + + entity->setVisuallyReady(visuallyReady); + if (bloomChanged) { updateBloomFromEntity(entity); } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 47ae8de9ad..490f9b9e6b 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -305,6 +305,7 @@ public: void setDynamic(bool value); virtual bool shouldBePhysical() const { return false; } + bool isVisuallyReady() const { return _visuallyReady; } bool getLocked() const; void setLocked(bool value); @@ -527,6 +528,7 @@ public: void removeCloneID(const QUuid& cloneID); const QVector getCloneIDs() const; void setCloneIDs(const QVector& cloneIDs); + void setVisuallyReady(bool visuallyReady) { _visuallyReady = visuallyReady; } signals: void requestRenderUpdate(); @@ -639,6 +641,7 @@ protected: EntityTreeElementPointer _element; // set by EntityTreeElement void* _physicsInfo { nullptr }; // set by EntitySimulation bool _simulated { false }; // set by EntitySimulation + bool _visuallyReady { true }; bool addActionInternal(EntitySimulationPointer simulation, EntityDynamicPointer action); bool removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation = nullptr); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 5d5344c9c8..774559a628 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -40,6 +40,7 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem( _type = EntityTypes::Model; _lastKnownCurrentFrame = -1; _color[0] = _color[1] = _color[2] = 0; + _visuallyReady = false; } const QString ModelEntityItem::getTextures() const { diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index a7dfa1a41e..c0ae61fdba 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -42,6 +42,7 @@ ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID) : EntityItem(en _shapeType = DEFAULT_SHAPE_TYPE; _compoundShapeURL = DEFAULT_COMPOUND_SHAPE_URL; + _visuallyReady = false; } EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index b275660c0f..c333a0c8c2 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -34,7 +34,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/emote.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ - "system/controllers/controllerScripts.js", + "system/controllers/controllerScripts.js" //"system/chat.js" ]; diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js new file mode 100644 index 0000000000..86a9e744e9 --- /dev/null +++ b/scripts/system/interstitialPage.js @@ -0,0 +1,456 @@ +// +// interstitialPage.js +// scripts/system +// +// Created by Dante Ruiz on 08/02/2018. +// Copyright 2012 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global Script, Controller, Overlays, Quat, MyAvatar, Entities, print, Vec3, AddressManager, Render, Window, Toolbars, + Camera, HMD, location, Account, Xform*/ + +(function() { + Script.include("/~/system/libraries/Xform.js"); + var DEBUG = false; + var MAX_X_SIZE = 3.8; + var EPSILON = 0.01; + var isVisible = false; + var VOLUME = 0.4; + var tune = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/crystals_and_voices.wav"); + var sample = null; + var MAX_LEFT_MARGIN = 1.9; + var INNER_CIRCLE_WIDTH = 4.7; + var DEFAULT_Z_OFFSET = 5.45; + var previousCameraMode = Camera.mode; + + var renderViewTask = Render.getConfig("RenderMainView"); + var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + var request = Script.require('request').request; + var BUTTON_PROPERTIES = { + text: "Interstitial" + }; + + var tablet = null; + var button = null; + + // Tips have a character limit of 69 + var userTips = [ + "Tip: Visit TheSpot to explore featured domains!", + "Tip: Visit our docs online to learn more about scripting!", + "Tip: Don't want others invading your personal space? Turn on the Bubble!", + "Tip: Want to make a friend? Shake hands with them in VR!", + "Tip: Enjoy live music? Visit Rust to dance your heart out!", + "Tip: Have you visited BodyMart to check out the new avatars recently?", + "Tip: Use the Create app to import models and create custom entities.", + "Tip: We're open source! Feel free to contribute to our code on GitHub!", + "Tip: What emotes have you used in the Emote app?", + "Tip: Take and share your snapshots with the everyone using the Snap app.", + "Tip: Did you know you can show websites in-world by creating a web entity?", + "Tip: Find out more information about domains by visiting our website!", + "Tip: Did you know you can get cool new apps from the Marketplace?", + "Tip: Print your snapshots from the Snap app to share with others!", + "Tip: Log in to make friends, visit new domains, and save avatars!" + ]; + + var DEFAULT_DIMENSIONS = { x: 20, y: 20, z: 20 }; + + var loadingSphereID = Overlays.addOverlay("model", { + name: "Loading-Sphere", + position: Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0.0, y: -1.0, z: 0.0}), Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.95, z: 0})), + orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), + url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/black-sphere.fbx", + dimensions: DEFAULT_DIMENSIONS, + alpha: 1, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + parentID: MyAvatar.SELF_ID + }); + + var anchorOverlay = Overlays.addOverlay("cube", { + dimensions: {x: 0.2, y: 0.2, z: 0.2}, + visible: false, + grabbable: false, + ignoreRayIntersection: true, + localPosition: {x: 0.0, y: getAnchorLocalYOffset(), z: DEFAULT_Z_OFFSET }, + orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), + solid: true, + drawInFront: true, + parentID: loadingSphereID + }); + + + var domainName = ""; + var domainNameTextID = Overlays.addOverlay("text3d", { + name: "Loading-Destination-Card-Text", + localPosition: { x: 0.0, y: 0.8, z: 0.0 }, + text: domainName, + textAlpha: 1, + backgroundAlpha: 0, + lineHeight: 0.42, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: anchorOverlay + }); + + var domainText = ""; + var domainDescription = Overlays.addOverlay("text3d", { + name: "Loading-Hostname", + localPosition: { x: 0.0, y: 0.32, z: 0.0 }, + text: domainText, + textAlpha: 1, + backgroundAlpha: 0, + lineHeight: 0.13, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: anchorOverlay + }); + + var toolTip = ""; + + var domainToolTip = Overlays.addOverlay("text3d", { + name: "Loading-Tooltip", + localPosition: { x: 0.0 , y: -1.6, z: 0.0 }, + text: toolTip, + textAlpha: 1, + backgroundAlpha: 0, + lineHeight: 0.13, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: anchorOverlay + }); + + var loadingToTheSpotID = Overlays.addOverlay("image3d", { + name: "Loading-Destination-Card-Text", + localPosition: { x: 0.0 , y: -1.8, z: 0.0 }, + url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/goTo_button.png", + alpha: 1, + dimensions: { x: 1.2, y: 0.6}, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: anchorOverlay + }); + + var loadingBarPlacard = Overlays.addOverlay("image3d", { + name: "Loading-Bar-Placard", + localPosition: { x: 0.0, y: -0.99, z: 0.3 }, + url: Script.resourcesPath() + "images/loadingBar_placard.png", + alpha: 1, + dimensions: { x: 4, y: 2.8}, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: anchorOverlay + }); + + var loadingBarProgress = Overlays.addOverlay("image3d", { + name: "Loading-Bar-Progress", + localPosition: { x: 0.0, y: -0.90, z: 0.0 }, + url: Script.resourcesPath() + "images/loadingBar_progress.png", + alpha: 1, + dimensions: {x: 3.8, y: 2.8}, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: anchorOverlay + }); + + var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update + var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; + var lastInterval = Date.now(); + var currentDomain = "no domain"; + var timer = null; + var target = 0; + + var connectionToDomainFailed = false; + + + function getAnchorLocalYOffset() { + var loadingSpherePosition = Overlays.getProperty(loadingSphereID, "position"); + var loadingSphereOrientation = Overlays.getProperty(loadingSphereID, "rotation"); + var overlayXform = new Xform(loadingSphereOrientation, loadingSpherePosition); + var worldToOverlayXform = overlayXform.inv(); + var headPosition = MyAvatar.getHeadPosition(); + var headPositionInOverlaySpace = worldToOverlayXform.xformPoint(headPosition); + return headPositionInOverlaySpace.y; + } + + function getLeftMargin(overlayID, text) { + var textSize = Overlays.textSize(overlayID, text); + var sizeDifference = ((INNER_CIRCLE_WIDTH - textSize.width) / 2); + var leftMargin = -(MAX_LEFT_MARGIN - sizeDifference); + return leftMargin; + } + + function lerp(a, b, t) { + return ((1 - t) * a + t * b); + } + + function resetValues() { + var properties = { + localPosition: { x: 1.85, y: -0.935, z: 0.0 }, + dimensions: { + x: 0.1, + y: 2.8 + } + }; + + Overlays.editOverlay(loadingBarProgress, properties); + } + + function startInterstitialPage() { + if (timer === null) { + updateOverlays(false); + startAudio(); + target = 0; + currentProgress = 0.1; + connectionToDomainFailed = false; + previousCameraMode = Camera.mode; + Camera.mode = "first person"; + timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + } + } + + function startAudio() { + sample = Audio.playSound(tune, { + localOnly: true, + position: MyAvatar.getHeadPosition(), + volume: VOLUME + }); + } + + function endAudio() { + sample.stop(); + sample = null; + } + + function domainChanged(domain) { + print("domain changed: " + domain); + if (domain !== currentDomain) { + MyAvatar.restoreAnimation(); + var name = location.placename; + domainName = name.charAt(0).toUpperCase() + name.slice(1); + var doRequest = true; + if (name.length === 0 && location.href === "file:///~/serverless/tutorial.json") { + domainName = "Serveless Domain (Tutorial)"; + doRequest = false; + } + var domainNameLeftMargin = getLeftMargin(domainNameTextID, domainName); + var textProperties = { + text: domainName, + leftMargin: domainNameLeftMargin + }; + + if (doRequest) { + var url = Account.metaverseServerURL + '/api/v1/places/' + domain; + request({ + uri: url + }, function(error, data) { + if (data.status === "success") { + var domainInfo = data.data; + var domainDescriptionText = domainInfo.place.description; + print("domainText: " + domainDescriptionText); + var leftMargin = getLeftMargin(domainDescription, domainDescriptionText); + var domainDescriptionProperties = { + text: domainDescriptionText, + leftMargin: leftMargin + }; + Overlays.editOverlay(domainDescription, domainDescriptionProperties); + } + }); + } else { + var domainDescriptionProperties = { + text: "" + }; + Overlays.editOverlay(domainDescription, domainDescriptionProperties); + } + + var randomIndex = Math.floor(Math.random() * userTips.length); + var tip = userTips[randomIndex]; + var tipLeftMargin = getLeftMargin(domainToolTip, tip); + var toolTipProperties = { + text: tip, + leftMargin: tipLeftMargin + }; + + Overlays.editOverlay(domainNameTextID, textProperties); + Overlays.editOverlay(domainToolTip, toolTipProperties); + + + startInterstitialPage(); + currentDomain = domain; + } + } + + var THE_PLACE = "hifi://TheSpot-dev"; + function clickedOnOverlay(overlayID, event) { + print(overlayID + " other: " + loadingToTheSpotID); + if (loadingToTheSpotID === overlayID) { + location.handleLookupString(THE_PLACE); + } + } + + var currentProgress = 0.1; + + function updateOverlays(physicsEnabled) { + var properties = { + visible: !physicsEnabled + }; + + var mainSphereProperties = { + visible: !physicsEnabled + }; + + var domainTextProperties = { + text: domainText, + visible: !physicsEnabled + }; + + var loadingBarProperties = { + dimensions: { x: 0.0, y: 2.8 }, + visible: !physicsEnabled + }; + + if (!HMD.active) { + MyAvatar.headOrientation = Quat.multiply(Quat.cancelOutRollAndPitch(MyAvatar.headOrientation), Quat.fromPitchYawRollDegrees(-3.0, 0, 0)); + } + + renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = physicsEnabled; + renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = physicsEnabled; + renderViewTask.getConfig("LightingModel")["enablePointLight"] = physicsEnabled; + Overlays.editOverlay(loadingSphereID, mainSphereProperties); + Overlays.editOverlay(loadingToTheSpotID, properties); + Overlays.editOverlay(domainNameTextID, properties); + Overlays.editOverlay(domainDescription, domainTextProperties); + Overlays.editOverlay(domainToolTip, properties); + Overlays.editOverlay(loadingBarPlacard, properties); + Overlays.editOverlay(loadingBarProgress, loadingBarProperties); + + if (physicsEnabled && !HMD.active) { + toolbar.writeProperty("visible", true); + } + + resetValues(); + + if (physicsEnabled) { + Camera.mode = previousCameraMode; + } + } + + + function scaleInterstitialPage(sensorToWorldScale) { + var yOffset = getAnchorLocalYOffset(); + var localPosition = { + x: 0.0, + y: yOffset, + z: 5.45 + }; + + Overlays.editOverlay(anchorOverlay, { localPosition: localPosition }); + } + + function update() { + var physicsEnabled = Window.isPhysicsEnabled(); + var thisInterval = Date.now(); + var deltaTime = (thisInterval - lastInterval); + lastInterval = thisInterval; + + var domainLoadingProgressPercentage = Window.domainLoadingProgress(); + + var progress = MAX_X_SIZE * domainLoadingProgressPercentage; + if (progress >= target) { + target = progress; + } + + if ((physicsEnabled && (currentProgress < MAX_X_SIZE))) { + target = MAX_X_SIZE; + } + + currentProgress = lerp(currentProgress, target, 0.2); + var properties = { + localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / MAX_X_SIZE))), y: -0.935, z: 0.0 }, + dimensions: { + x: currentProgress, + y: 2.8 + } + }; + + Overlays.editOverlay(loadingBarProgress, properties); + if ((physicsEnabled && (currentProgress >= (MAX_X_SIZE - EPSILON)))) { + updateOverlays((physicsEnabled || connectionToDomainFailed)); + endAudio(); + timer = null; + return; + } + + timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + } + + Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); + location.hostChanged.connect(domainChanged); + location.lookupResultsFinished.connect(function() { + Script.setTimeout(function() { + connectionToDomainFailed = !location.isConnected; + }, 1200); + }); + + MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); + + var toggle = true; + if (DEBUG) { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton(BUTTON_PROPERTIES); + + button.clicked.connect(function() { + toggle = !toggle; + updateOverlays(toggle); + }); + } + + function cleanup() { + Overlays.deleteOverlay(loadingSphereID); + Overlays.deleteOverlay(loadingToTheSpotID); + Overlays.deleteOverlay(domainNameTextID); + Overlays.deleteOverlay(domainDescription); + Overlays.deleteOverlay(domainToolTip); + Overlays.deleteOverlay(loadingBarPlacard); + Overlays.deleteOverlay(loadingBarProgress); + Overlays.deleteOverlay(anchorOverlay); + + if (DEBUG) { + tablet.removeButton(button); + } + + renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = true; + renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = true; + renderViewTask.getConfig("LightingModel")["enablePointLight"] = true; + if (!HMD.active) { + toolbar.writeProperty("visible", true); + } + } + + Script.scriptEnding.connect(cleanup); +}());