Merge branch 'master' of https://github.com/highfidelity/hifi into loginInitiative2

This commit is contained in:
Wayne Chen 2018-10-19 09:25:57 -07:00
commit e32460ed96
37 changed files with 495 additions and 191 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 175 KiB

View file

@ -39,6 +39,7 @@ Item {
property string sendingPubliclyEffectImage; property string sendingPubliclyEffectImage;
property var http; property var http;
property var listModelName; property var listModelName;
property var keyboardContainer: nil;
// This object is always used in a popup or full-screen Wallet section. // This object is always used in a popup or full-screen Wallet section.
// This MouseArea is used to prevent a user from being // This MouseArea is used to prevent a user from being
@ -1125,8 +1126,7 @@ Item {
checked: Settings.getValue("sendAssetsNearbyPublicly", true); checked: Settings.getValue("sendAssetsNearbyPublicly", true);
text: "Show Effect" text: "Show Effect"
// Anchors // Anchors
anchors.top: messageContainer.bottom; anchors.verticalCenter: bottomBarContainer.verticalCenter;
anchors.topMargin: 16;
anchors.left: parent.left; anchors.left: parent.left;
anchors.leftMargin: 20; anchors.leftMargin: 20;
width: 130; width: 130;
@ -1168,6 +1168,9 @@ Item {
lightboxPopup.visible = false; lightboxPopup.visible = false;
} }
lightboxPopup.visible = true; lightboxPopup.visible = true;
if (keyboardContainer) {
keyboardContainer.keyboardRaised = false;
}
} }
} }
} }
@ -1178,8 +1181,8 @@ Item {
anchors.leftMargin: 20; anchors.leftMargin: 20;
anchors.right: parent.right; anchors.right: parent.right;
anchors.rightMargin: 20; anchors.rightMargin: 20;
anchors.bottom: parent.bottom; anchors.top: messageContainer.bottom;
anchors.bottomMargin: 20; anchors.topMargin: 20;
height: 60; height: 60;
// "CANCEL" button // "CANCEL" button
@ -1187,11 +1190,11 @@ Item {
id: cancelButton_sendAssetStep; id: cancelButton_sendAssetStep;
color: root.assetName === "" ? hifi.buttons.noneBorderlessWhite : hifi.buttons.noneBorderlessGray; color: root.assetName === "" ? hifi.buttons.noneBorderlessWhite : hifi.buttons.noneBorderlessGray;
colorScheme: hifi.colorSchemes.dark; colorScheme: hifi.colorSchemes.dark;
anchors.left: parent.left; anchors.right: sendButton.left;
anchors.leftMargin: 24; anchors.rightMargin: 24;
anchors.verticalCenter: parent.verticalCenter; anchors.verticalCenter: parent.verticalCenter;
height: 40; height: 40;
width: 150; width: 100;
text: "CANCEL"; text: "CANCEL";
onClicked: { onClicked: {
resetSendAssetData(); resetSendAssetData();
@ -1205,10 +1208,10 @@ Item {
color: hifi.buttons.blue; color: hifi.buttons.blue;
colorScheme: root.assetName === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light; colorScheme: root.assetName === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
anchors.right: parent.right; anchors.right: parent.right;
anchors.rightMargin: 24; anchors.rightMargin: 0;
anchors.verticalCenter: parent.verticalCenter; anchors.verticalCenter: parent.verticalCenter;
height: 40; height: 40;
width: 150; width: 100;
text: "SUBMIT"; text: "SUBMIT";
onClicked: { onClicked: {
if (root.assetName === "" && parseInt(amountTextField.text) > parseInt(balanceText.text)) { if (root.assetName === "" && parseInt(amountTextField.text) > parseInt(balanceText.text)) {

View file

@ -158,6 +158,7 @@ Rectangle {
listModelName: "Gift Connections"; listModelName: "Gift Connections";
z: 998; z: 998;
visible: root.activeView === "giftAsset"; visible: root.activeView === "giftAsset";
keyboardContainer: root;
anchors.fill: parent; anchors.fill: parent;
parentAppTitleBarHeight: 70; parentAppTitleBarHeight: 70;
parentAppNavBarHeight: 0; parentAppNavBarHeight: 0;
@ -585,7 +586,7 @@ Rectangle {
visible: purchasesModel.count !== 0; visible: purchasesModel.count !== 0;
clip: true; clip: true;
model: purchasesModel; model: purchasesModel;
snapMode: ListView.SnapToItem; snapMode: ListView.NoSnap;
// Anchors // Anchors
anchors.top: separator.bottom; anchors.top: separator.bottom;
anchors.left: parent.left; anchors.left: parent.left;

View file

@ -354,6 +354,7 @@ Rectangle {
listModelName: "Send Money Connections"; listModelName: "Send Money Connections";
z: 997; z: 997;
visible: root.activeView === "sendMoney"; visible: root.activeView === "sendMoney";
keyboardContainer: root;
anchors.fill: parent; anchors.fill: parent;
parentAppTitleBarHeight: titleBarContainer.height; parentAppTitleBarHeight: titleBarContainer.height;
parentAppNavBarHeight: tabButtonsContainer.height; parentAppNavBarHeight: tabButtonsContainer.height;

View file

@ -8558,6 +8558,16 @@ QUuid Application::getTabletFrameID() const {
return HMD->getCurrentTabletFrameID(); return HMD->getCurrentTabletFrameID();
} }
QVector<QUuid> Application::getTabletIDs() const {
// Most important overlays first.
QVector<QUuid> result;
auto HMD = DependencyManager::get<HMDScriptingInterface>();
result << HMD->getCurrentTabletScreenID();
result << HMD->getCurrentHomeButtonID();
result << HMD->getCurrentTabletFrameID();
return result;
}
void Application::setAvatarOverrideUrl(const QUrl& url, bool save) { void Application::setAvatarOverrideUrl(const QUrl& url, bool save) {
_avatarOverrideUrl = url; _avatarOverrideUrl = url;
_saveAvatarOverrideUrl = save; _saveAvatarOverrideUrl = save;

View file

@ -299,6 +299,7 @@ public:
OverlayID getTabletScreenID() const; OverlayID getTabletScreenID() const;
OverlayID getTabletHomeButtonID() const; OverlayID getTabletHomeButtonID() const;
QUuid getTabletFrameID() const; // may be an entity or an overlay QUuid getTabletFrameID() const; // may be an entity or an overlay
QVector<QUuid> getTabletIDs() const; // In order of most important IDs first.
void setAvatarOverrideUrl(const QUrl& url, bool save); void setAvatarOverrideUrl(const QUrl& url, bool save);
void clearAvatarOverrideUrl() { _avatarOverrideUrl = QUrl(); _saveAvatarOverrideUrl = false; } void clearAvatarOverrideUrl() { _avatarOverrideUrl = QUrl(); _saveAvatarOverrideUrl = false; }

View file

@ -108,6 +108,9 @@ AvatarBookmarks::AvatarBookmarks() {
if (!QFile::copy(defaultBookmarksFilename, _bookmarksFilename)) { if (!QFile::copy(defaultBookmarksFilename, _bookmarksFilename)) {
qDebug() << "failed to copy" << defaultBookmarksFilename << "to" << _bookmarksFilename; qDebug() << "failed to copy" << defaultBookmarksFilename << "to" << _bookmarksFilename;
} else {
QFile bookmarksFile(_bookmarksFilename);
bookmarksFile.setPermissions(bookmarksFile.permissions() | QFile::WriteUser);
} }
} }
readFromFile(); readFromFile();

View file

@ -629,8 +629,6 @@ public:
const MyHead* getMyHead() const; const MyHead* getMyHead() const;
Q_INVOKABLE void toggleSmoothPoleVectors() { _skeletonModel->getRig().toggleSmoothPoleVectors(); };
/**jsdoc /**jsdoc
* Get the current position of the avatar's "Head" joint. * Get the current position of the avatar's "Head" joint.
* @function MyAvatar.getHeadPosition * @function MyAvatar.getHeadPosition

View file

@ -46,7 +46,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
} }
glm::mat4 hipsMat; glm::mat4 hipsMat;
if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState())) { if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && myAvatar->getHMDLeanRecenterEnabled()) {
// then we use center of gravity model // then we use center of gravity model
hipsMat = myAvatar->deriveBodyUsingCgModel(); hipsMat = myAvatar->deriveBodyUsingCgModel();
} else { } else {

View file

@ -532,6 +532,8 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
bool visibleOnly, bool collidableOnly) { bool visibleOnly, bool collidableOnly) {
float bestDistance = std::numeric_limits<float>::max(); float bestDistance = std::numeric_limits<float>::max();
bool bestIsFront = false; bool bestIsFront = false;
bool bestIsTablet = false;
auto tabletIDs = qApp->getTabletIDs();
QMutexLocker locker(&_mutex); QMutexLocker locker(&_mutex);
RayToOverlayIntersectionResult result; RayToOverlayIntersectionResult result;
@ -554,10 +556,11 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance, if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance,
thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) { thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) {
bool isDrawInFront = thisOverlay->getDrawInFront(); bool isDrawInFront = thisOverlay->getDrawInFront();
if ((bestIsFront && isDrawInFront && thisDistance < bestDistance) bool isTablet = tabletIDs.contains(thisID);
|| (!bestIsFront && (isDrawInFront || thisDistance < bestDistance))) { if ((isDrawInFront && !bestIsFront && !bestIsTablet)
|| ((isTablet || isDrawInFront || !bestIsFront) && thisDistance < bestDistance)) {
bestIsFront = isDrawInFront; bestIsFront = isDrawInFront;
bestIsTablet = isTablet;
bestDistance = thisDistance; bestDistance = thisDistance;
result.intersects = true; result.intersects = true;
result.distance = thisDistance; result.distance = thisDistance;
@ -828,40 +831,12 @@ PointerEvent Overlays::calculateOverlayPointerEvent(OverlayID overlayID, PickRay
} }
RayToOverlayIntersectionResult Overlays::findRayIntersectionForMouseEvent(PickRay ray) {
QVector<OverlayID> overlaysToInclude;
QVector<OverlayID> overlaysToDiscard;
RayToOverlayIntersectionResult rayPickResult;
// first priority is tablet screen
overlaysToInclude << qApp->getTabletScreenID();
rayPickResult = findRayIntersectionVector(ray, true, overlaysToInclude, overlaysToDiscard);
if (rayPickResult.intersects) {
return rayPickResult;
}
// then tablet home button
overlaysToInclude.clear();
overlaysToInclude << qApp->getTabletHomeButtonID();
rayPickResult = findRayIntersectionVector(ray, true, overlaysToInclude, overlaysToDiscard);
if (rayPickResult.intersects) {
return rayPickResult;
}
// then tablet frame
overlaysToInclude.clear();
overlaysToInclude << OverlayID(qApp->getTabletFrameID());
rayPickResult = findRayIntersectionVector(ray, true, overlaysToInclude, overlaysToDiscard);
if (rayPickResult.intersects) {
return rayPickResult;
}
// then whatever
return findRayIntersection(ray);
}
bool Overlays::mousePressEvent(QMouseEvent* event) { bool Overlays::mousePressEvent(QMouseEvent* event) {
PerformanceTimer perfTimer("Overlays::mousePressEvent"); PerformanceTimer perfTimer("Overlays::mousePressEvent");
PickRay ray = qApp->computePickRay(event->x(), event->y()); PickRay ray = qApp->computePickRay(event->x(), event->y());
RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector<OverlayID>(),
QVector<OverlayID>());
if (rayPickResult.intersects) { if (rayPickResult.intersects) {
_currentClickingOnOverlayID = rayPickResult.overlayID; _currentClickingOnOverlayID = rayPickResult.overlayID;
@ -901,7 +876,8 @@ bool Overlays::mouseDoublePressEvent(QMouseEvent* event) {
PerformanceTimer perfTimer("Overlays::mouseDoublePressEvent"); PerformanceTimer perfTimer("Overlays::mouseDoublePressEvent");
PickRay ray = qApp->computePickRay(event->x(), event->y()); PickRay ray = qApp->computePickRay(event->x(), event->y());
RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector<OverlayID>(),
QVector<OverlayID>());
if (rayPickResult.intersects) { if (rayPickResult.intersects) {
_currentClickingOnOverlayID = rayPickResult.overlayID; _currentClickingOnOverlayID = rayPickResult.overlayID;
@ -964,7 +940,8 @@ bool Overlays::mouseReleaseEvent(QMouseEvent* event) {
PerformanceTimer perfTimer("Overlays::mouseReleaseEvent"); PerformanceTimer perfTimer("Overlays::mouseReleaseEvent");
PickRay ray = qApp->computePickRay(event->x(), event->y()); PickRay ray = qApp->computePickRay(event->x(), event->y());
RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector<OverlayID>(),
QVector<OverlayID>());
if (rayPickResult.intersects) { if (rayPickResult.intersects) {
auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Release); auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Release);
mouseReleasePointerEvent(rayPickResult.overlayID, pointerEvent); mouseReleasePointerEvent(rayPickResult.overlayID, pointerEvent);
@ -993,7 +970,8 @@ bool Overlays::mouseMoveEvent(QMouseEvent* event) {
PerformanceTimer perfTimer("Overlays::mouseMoveEvent"); PerformanceTimer perfTimer("Overlays::mouseMoveEvent");
PickRay ray = qApp->computePickRay(event->x(), event->y()); PickRay ray = qApp->computePickRay(event->x(), event->y());
RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector<OverlayID>(),
QVector<OverlayID>());
if (rayPickResult.intersects) { if (rayPickResult.intersects) {
auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Move); auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Move);
mouseMovePointerEvent(rayPickResult.overlayID, pointerEvent); mouseMovePointerEvent(rayPickResult.overlayID, pointerEvent);

View file

@ -44,8 +44,7 @@ void OverlayPropertyResultFromScriptValue(const QScriptValue& object, OverlayPro
const OverlayID UNKNOWN_OVERLAY_ID = OverlayID(); const OverlayID UNKNOWN_OVERLAY_ID = OverlayID();
/**jsdoc /**jsdoc
* The result of a {@link PickRay} search using {@link Overlays.findRayIntersection|findRayIntersection} or * The result of a {@link PickRay} search using {@link Overlays.findRayIntersection|findRayIntersection}.
* {@link Overlays.findRayIntersectionVector|findRayIntersectionVector}.
* @typedef {object} Overlays.RayToOverlayIntersectionResult * @typedef {object} Overlays.RayToOverlayIntersectionResult
* @property {boolean} intersects - <code>true</code> if the {@link PickRay} intersected with a 3D overlay, otherwise * @property {boolean} intersects - <code>true</code> if the {@link PickRay} intersected with a 3D overlay, otherwise
* <code>false</code>. * <code>false</code>.
@ -383,7 +382,11 @@ public slots:
OverlayPropertyResult getOverlaysProperties(const QVariant& overlaysProperties); OverlayPropertyResult getOverlaysProperties(const QVariant& overlaysProperties);
/**jsdoc /**jsdoc
* Find the closest 3D overlay intersected by a {@link PickRay}. * Find the closest 3D overlay intersected by a {@link PickRay}. Overlays with their <code>drawInFront</code> property set
* to <code>true</code> have priority over overlays that don't, except that tablet overlays have priority over any
* <code>drawInFront</code> overlays behind them. I.e., if a <code>drawInFront</code> overlay is behind one that isn't
* <code>drawInFront</code>, the <code>drawInFront</code> overlay is returned, but if a tablet overlay is in front of a
* <code>drawInFront</code> overlay, the tablet overlay is returned.
* @function Overlays.findRayIntersection * @function Overlays.findRayIntersection
* @param {PickRay} pickRay - The PickRay to use for finding overlays. * @param {PickRay} pickRay - The PickRay to use for finding overlays.
* @param {boolean} [precisionPicking=false] - <em>Unused</em>; exists to match Entity API. * @param {boolean} [precisionPicking=false] - <em>Unused</em>; exists to match Entity API.
@ -750,8 +753,6 @@ private:
OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID }; OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID };
OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID }; OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID };
RayToOverlayIntersectionResult findRayIntersectionForMouseEvent(PickRay ray);
private slots: private slots:
void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event); void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event);
void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event); void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event);

View file

@ -1253,7 +1253,6 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) {
const bool ENABLE_POLE_VECTORS = true; const bool ENABLE_POLE_VECTORS = true;
const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f;
if (leftHandEnabled) { if (leftHandEnabled) {
@ -1279,33 +1278,16 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector);
if (usePoleVector) { if (usePoleVector) {
glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector);
if (_smoothPoleVectors) {
// smooth toward desired pole vector from previous pole vector... to reduce jitter
if (!_prevLeftHandPoleVectorValid) {
_prevLeftHandPoleVectorValid = true;
_prevLeftHandPoleVector = sensorPoleVector;
}
glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, sensorPoleVector);
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR);
_prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector;
} else {
_prevLeftHandPoleVector = sensorPoleVector;
}
_animVars.set("leftHandPoleVectorEnabled", true); _animVars.set("leftHandPoleVectorEnabled", true);
_animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X);
_animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftHandPoleVector)); _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, sensorPoleVector));
} else { } else {
_prevLeftHandPoleVectorValid = false;
_animVars.set("leftHandPoleVectorEnabled", false); _animVars.set("leftHandPoleVectorEnabled", false);
} }
} else { } else {
_prevLeftHandPoleVectorValid = false;
_animVars.set("leftHandPoleVectorEnabled", false); _animVars.set("leftHandPoleVectorEnabled", false);
} }
} else { } else {
_prevLeftHandPoleVectorValid = false;
_animVars.set("leftHandPoleVectorEnabled", false); _animVars.set("leftHandPoleVectorEnabled", false);
_animVars.unset("leftHandPosition"); _animVars.unset("leftHandPosition");
@ -1344,33 +1326,16 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector);
if (usePoleVector) { if (usePoleVector) {
glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector);
if (_smoothPoleVectors) {
// smooth toward desired pole vector from previous pole vector... to reduce jitter
if (!_prevRightHandPoleVectorValid) {
_prevRightHandPoleVectorValid = true;
_prevRightHandPoleVector = sensorPoleVector;
}
glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, sensorPoleVector);
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR);
_prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector;
} else {
_prevRightHandPoleVector = sensorPoleVector;
}
_animVars.set("rightHandPoleVectorEnabled", true); _animVars.set("rightHandPoleVectorEnabled", true);
_animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X);
_animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightHandPoleVector)); _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, sensorPoleVector));
} else { } else {
_prevRightHandPoleVectorValid = false;
_animVars.set("rightHandPoleVectorEnabled", false); _animVars.set("rightHandPoleVectorEnabled", false);
} }
} else { } else {
_prevRightHandPoleVectorValid = false;
_animVars.set("rightHandPoleVectorEnabled", false); _animVars.set("rightHandPoleVectorEnabled", false);
} }
} else { } else {
_prevRightHandPoleVectorValid = false;
_animVars.set("rightHandPoleVectorEnabled", false); _animVars.set("rightHandPoleVectorEnabled", false);
_animVars.unset("rightHandPosition"); _animVars.unset("rightHandPosition");

View file

@ -227,7 +227,6 @@ public:
const AnimVariantMap& getAnimVars() const { return _lastAnimVars; } const AnimVariantMap& getAnimVars() const { return _lastAnimVars; }
const AnimContext::DebugStateMachineMap& getStateMachineMap() const { return _lastContext.getStateMachineMap(); } const AnimContext::DebugStateMachineMap& getStateMachineMap() const { return _lastContext.getStateMachineMap(); }
void toggleSmoothPoleVectors() { _smoothPoleVectors = !_smoothPoleVectors; };
signals: signals:
void onLoadComplete(); void onLoadComplete();
@ -381,14 +380,6 @@ protected:
glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z }; // sensor space glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z }; // sensor space
bool _prevLeftFootPoleVectorValid { false }; bool _prevLeftFootPoleVectorValid { false };
glm::vec3 _prevRightHandPoleVector{ -Vectors::UNIT_Z }; // sensor space
bool _prevRightHandPoleVectorValid{ false };
glm::vec3 _prevLeftHandPoleVector{ -Vectors::UNIT_Z }; // sensor space
bool _prevLeftHandPoleVectorValid{ false };
bool _smoothPoleVectors { false };
int _rigId; int _rigId;
bool _headEnabled { false }; bool _headEnabled { false };

View file

@ -67,6 +67,7 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
(&::gpu::gl::GLBackend::do_clearFramebuffer), (&::gpu::gl::GLBackend::do_clearFramebuffer),
(&::gpu::gl::GLBackend::do_blit), (&::gpu::gl::GLBackend::do_blit),
(&::gpu::gl::GLBackend::do_generateTextureMips), (&::gpu::gl::GLBackend::do_generateTextureMips),
(&::gpu::gl::GLBackend::do_generateTextureMipsWithPipeline),
(&::gpu::gl::GLBackend::do_advance), (&::gpu::gl::GLBackend::do_advance),
@ -166,6 +167,10 @@ GLBackend::GLBackend() {
GLBackend::~GLBackend() {} GLBackend::~GLBackend() {}
void GLBackend::shutdown() { void GLBackend::shutdown() {
if (_mipGenerationFramebufferId) {
glDeleteFramebuffers(1, &_mipGenerationFramebufferId);
_mipGenerationFramebufferId = 0;
}
killInput(); killInput();
killTransform(); killTransform();
killTextureManagementStage(); killTextureManagementStage();

View file

@ -288,6 +288,7 @@ public:
virtual void do_setIndexBuffer(const Batch& batch, size_t paramOffset) final; virtual void do_setIndexBuffer(const Batch& batch, size_t paramOffset) final;
virtual void do_setIndirectBuffer(const Batch& batch, size_t paramOffset) final; virtual void do_setIndirectBuffer(const Batch& batch, size_t paramOffset) final;
virtual void do_generateTextureMips(const Batch& batch, size_t paramOffset) final; virtual void do_generateTextureMips(const Batch& batch, size_t paramOffset) final;
virtual void do_generateTextureMipsWithPipeline(const Batch& batch, size_t paramOffset) final;
// Transform Stage // Transform Stage
virtual void do_setModelTransform(const Batch& batch, size_t paramOffset) final; virtual void do_setModelTransform(const Batch& batch, size_t paramOffset) final;
@ -407,6 +408,8 @@ public:
protected: protected:
virtual GLint getRealUniformLocation(GLint location) const; virtual GLint getRealUniformLocation(GLint location) const;
virtual void draw(GLenum mode, uint32 numVertices, uint32 startVertex) = 0;
void recycle() const override; void recycle() const override;
// FIXME instead of a single flag, create a features struct similar to // FIXME instead of a single flag, create a features struct similar to
@ -696,6 +699,8 @@ protected:
virtual void initTextureManagementStage(); virtual void initTextureManagementStage();
virtual void killTextureManagementStage(); virtual void killTextureManagementStage();
GLuint _mipGenerationFramebufferId{ 0 };
typedef void (GLBackend::*CommandCall)(const Batch&, size_t); typedef void (GLBackend::*CommandCall)(const Batch&, size_t);
static CommandCall _commandCalls[Batch::NUM_COMMANDS]; static CommandCall _commandCalls[Batch::NUM_COMMANDS];
friend class GLState; friend class GLState;

View file

@ -79,3 +79,55 @@ void GLBackend::do_generateTextureMips(const Batch& batch, size_t paramOffset) {
object->generateMips(); object->generateMips();
} }
void GLBackend::do_generateTextureMipsWithPipeline(const Batch& batch, size_t paramOffset) {
TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint);
if (!resourceTexture) {
return;
}
// Always make sure the GLObject is in sync
GLTexture* object = syncGPUObject(resourceTexture);
if (object) {
GLuint to = object->_texture;
glActiveTexture(GL_TEXTURE0 + gpu::slot::texture::MipCreationInput);
glBindTexture(object->_target, to);
(void)CHECK_GL_ERROR();
} else {
return;
}
auto numMips = batch._params[paramOffset + 1]._int;
if (numMips < 0) {
numMips = resourceTexture->getNumMips();
} else {
numMips = std::min(numMips, (int)resourceTexture->getNumMips());
}
if (_mipGenerationFramebufferId == 0) {
glGenFramebuffers(1, &_mipGenerationFramebufferId);
Q_ASSERT(_mipGenerationFramebufferId > 0);
}
glBindFramebuffer(GL_FRAMEBUFFER, _mipGenerationFramebufferId);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
for (int level = 1; level < numMips; level++) {
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, object->_id, level);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, level - 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level - 1);
const auto mipDimensions = resourceTexture->evalMipDimensions(level);
glViewport(0, 0, mipDimensions.x, mipDimensions.y);
draw(GL_TRIANGLE_STRIP, 4, 0);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, numMips - 1);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
resetOutputStage();
// Restore viewport
ivec4& vp = _transform._viewport;
glViewport(vp.x, vp.y, vp.z, vp.w);
}

View file

@ -20,6 +20,29 @@ using namespace gpu::gl41;
const std::string GL41Backend::GL41_VERSION { "GL41" }; const std::string GL41Backend::GL41_VERSION { "GL41" };
void GL41Backend::draw(GLenum mode, uint32 numVertices, uint32 startVertex) {
if (isStereo()) {
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
glDrawArraysInstanced(mode, startVertex, numVertices, 2);
#else
setupStereoSide(0);
glDrawArrays(mode, startVertex, numVertices);
setupStereoSide(1);
glDrawArrays(mode, startVertex, numVertices);
#endif
_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_draw(const Batch& batch, size_t paramOffset) { void GL41Backend::do_draw(const Batch& batch, size_t paramOffset) {
Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint;
GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType];

View file

@ -130,6 +130,9 @@ public:
}; };
protected: protected:
void draw(GLenum mode, uint32 numVertices, uint32 startVertex) override;
GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; GLuint getFramebufferID(const FramebufferPointer& framebuffer) override;
GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override;

View file

@ -42,6 +42,30 @@ void GL45Backend::recycle() const {
Parent::recycle(); Parent::recycle();
} }
void GL45Backend::draw(GLenum mode, uint32 numVertices, uint32 startVertex) {
if (isStereo()) {
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
glDrawArraysInstanced(mode, startVertex, numVertices, 2);
#else
setupStereoSide(0);
glDrawArrays(mode, startVertex, numVertices);
setupStereoSide(1);
glDrawArrays(mode, startVertex, numVertices);
#endif
_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_draw(const Batch& batch, size_t paramOffset) { void GL45Backend::do_draw(const Batch& batch, size_t paramOffset) {
Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint;
GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType];

View file

@ -229,6 +229,7 @@ public:
protected: protected:
void draw(GLenum mode, uint32 numVertices, uint32 startVertex) override;
void recycle() const override; void recycle() const override;
GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; GLuint getFramebufferID(const FramebufferPointer& framebuffer) override;

View file

@ -20,6 +20,29 @@ using namespace gpu::gles;
const std::string GLESBackend::GLES_VERSION { "GLES" }; const std::string GLESBackend::GLES_VERSION { "GLES" };
void GLESBackend::draw(GLenum mode, uint32 numVertices, uint32 startVertex) {
if (isStereo()) {
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
glDrawArraysInstanced(mode, startVertex, numVertices, 2);
#else
setupStereoSide(0);
glDrawArrays(mode, startVertex, numVertices);
setupStereoSide(1);
glDrawArrays(mode, startVertex, numVertices);
#endif
_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 GLESBackend::do_draw(const Batch& batch, size_t paramOffset) { void GLESBackend::do_draw(const Batch& batch, size_t paramOffset) {
Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint;
GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType];

View file

@ -126,6 +126,9 @@ public:
}; };
protected: protected:
void draw(GLenum mode, uint32 numVertices, uint32 startVertex) override;
GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; GLuint getFramebufferID(const FramebufferPointer& framebuffer) override;
GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override;

View file

@ -426,6 +426,13 @@ void Batch::generateTextureMips(const TexturePointer& texture) {
_params.emplace_back(_textures.cache(texture)); _params.emplace_back(_textures.cache(texture));
} }
void Batch::generateTextureMipsWithPipeline(const TexturePointer& texture, int numMips) {
ADD_COMMAND(generateTextureMipsWithPipeline);
_params.emplace_back(_textures.cache(texture));
_params.emplace_back(numMips);
}
void Batch::beginQuery(const QueryPointer& query) { void Batch::beginQuery(const QueryPointer& query) {
ADD_COMMAND(beginQuery); ADD_COMMAND(beginQuery);

View file

@ -226,6 +226,8 @@ public:
// Generate the mips for a texture // Generate the mips for a texture
void generateTextureMips(const TexturePointer& texture); void generateTextureMips(const TexturePointer& texture);
// Generate the mips for a texture using the current pipeline
void generateTextureMipsWithPipeline(const TexturePointer& destTexture, int numMips = -1);
// Query Section // Query Section
void beginQuery(const QueryPointer& query); void beginQuery(const QueryPointer& query);
@ -326,6 +328,7 @@ public:
COMMAND_clearFramebuffer, COMMAND_clearFramebuffer,
COMMAND_blit, COMMAND_blit,
COMMAND_generateTextureMips, COMMAND_generateTextureMips,
COMMAND_generateTextureMipsWithPipeline,
COMMAND_advance, COMMAND_advance,

View file

@ -0,0 +1,20 @@
<!
// MipGeneration.slh
// libraries/gpu/src
//
// Created by Olivier Prat on 10/16/18.
// Copyright 2018 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 MIP_GENERATION_SLH@>
<@def MIP_GENERATION_SLH@>
<@include gpu/ShaderConstants.h@>
layout(binding=GPU_TEXTURE_MIP_CREATION_INPUT) uniform sampler2D texMap;
in vec2 varTexCoord0;
<@endif@>

View file

@ -21,6 +21,9 @@
#define GPU_TEXTURE_TRANSFORM_OBJECT 31 #define GPU_TEXTURE_TRANSFORM_OBJECT 31
// Mip creation
#define GPU_TEXTURE_MIP_CREATION_INPUT 30
#define GPU_STORAGE_TRANSFORM_OBJECT 7 #define GPU_STORAGE_TRANSFORM_OBJECT 7
#define GPU_ATTR_POSITION 0 #define GPU_ATTR_POSITION 0
@ -67,7 +70,8 @@ enum Buffer {
namespace texture { namespace texture {
enum Texture { enum Texture {
ObjectTransforms = GPU_TEXTURE_TRANSFORM_OBJECT, ObjectTransforms = GPU_TEXTURE_TRANSFORM_OBJECT,
}; MipCreationInput = GPU_TEXTURE_MIP_CREATION_INPUT,
};
} // namespace texture } // namespace texture
namespace storage { namespace storage {

View file

@ -146,8 +146,10 @@ void DomainHandler::hardReset() {
} }
bool DomainHandler::isHardRefusal(int reasonCode) { bool DomainHandler::isHardRefusal(int reasonCode) {
return (reasonCode == (int)ConnectionRefusedReason::ProtocolMismatch || reasonCode == (int)ConnectionRefusedReason::NotAuthorized || return (reasonCode == (int)ConnectionRefusedReason::ProtocolMismatch ||
reasonCode == (int)ConnectionRefusedReason::TimedOut); reasonCode == (int)ConnectionRefusedReason::TooManyUsers ||
reasonCode == (int)ConnectionRefusedReason::NotAuthorized ||
reasonCode == (int)ConnectionRefusedReason::TimedOut);
} }
bool DomainHandler::getInterstitialModeEnabled() const { bool DomainHandler::getInterstitialModeEnabled() const {
@ -496,7 +498,7 @@ bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) {
case ConnectionRefusedReason::LoginError: case ConnectionRefusedReason::LoginError:
case ConnectionRefusedReason::NotAuthorized: case ConnectionRefusedReason::NotAuthorized:
return true; return true;
default: default:
case ConnectionRefusedReason::Unknown: case ConnectionRefusedReason::Unknown:
case ConnectionRefusedReason::ProtocolMismatch: case ConnectionRefusedReason::ProtocolMismatch:

View file

@ -24,7 +24,7 @@
this.reticleMinY = MARGIN; this.reticleMinY = MARGIN;
this.reticleMaxY; this.reticleMaxY;
this.parameters = ControllerDispatcherUtils.makeDispatcherModuleParameters( this.parameters = ControllerDispatcherUtils.makeDispatcherModuleParameters(
540, 160, // Same as webSurfaceLaserInput.
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[], [],
100, 100,
@ -63,7 +63,6 @@
this.processLaser = function(controllerData) { this.processLaser = function(controllerData) {
var controllerLocation = controllerData.controllerLocations[this.hand]; var controllerLocation = controllerData.controllerLocations[this.hand];
// var otherModuleRunning = this.getOtherModule().running;
if ((controllerData.triggerValues[this.hand] < ControllerDispatcherUtils.TRIGGER_ON_VALUE || !controllerLocation.valid) || if ((controllerData.triggerValues[this.hand] < ControllerDispatcherUtils.TRIGGER_ON_VALUE || !controllerLocation.valid) ||
this.pointingAtTablet(controllerData)) { this.pointingAtTablet(controllerData)) {
return false; return false;

View file

@ -28,7 +28,7 @@ Script.include("/~/system/libraries/utils.js");
this.reticleMaxY = null; this.reticleMaxY = null;
this.parameters = makeDispatcherModuleParameters( this.parameters = makeDispatcherModuleParameters(
160, 165, // Lower priority than webSurfaceLaserInput and hudOverlayPointer.
this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"],
[], [],
100, 100,
@ -127,29 +127,41 @@ Script.include("/~/system/libraries/utils.js");
}; };
this.run = function(controllerData) { this.run = function(controllerData) {
var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightTabletStylusInput" : "LeftTabletStylusInput"); var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightTabletStylusInput" : "LeftTabletStylusInput");
if (tabletStylusInput) { if (tabletStylusInput) {
var tabletReady = tabletStylusInput.isReady(controllerData); var tabletReady = tabletStylusInput.isReady(controllerData);
if (tabletReady.active) { if (tabletReady.active) {
return this.exitModule(); return this.exitModule();
} }
} }
var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput"); var webLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
if (overlayLaser) { ? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput");
var overlayLaserReady = overlayLaser.isReady(controllerData); if (webLaser) {
var webLaserReady = webLaser.isReady(controllerData);
var target = controllerData.rayPicks[this.hand].objectID; var target = controllerData.rayPicks[this.hand].objectID;
this.sendPointingAtData(controllerData); this.sendPointingAtData(controllerData);
if (overlayLaserReady.active && this.pointingAtTablet(target)) { if (webLaserReady.active && this.pointingAtTablet(target)) {
return this.exitModule(); return this.exitModule();
} }
} }
var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); if (!controllerData.triggerClicks[this.hand]) { // Don't grab if trigger pressed when laser starts intersecting.
var hudLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightHudOverlayPointer" : "LeftHudOverlayPointer");
if (hudLaser) {
var hudLaserReady = hudLaser.isReady(controllerData);
if (hudLaserReady.active) {
return this.exitModule();
}
}
}
var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay");
if (nearOverlay) { if (nearOverlay) {
var nearOverlayReady = nearOverlay.isReady(controllerData); var nearOverlayReady = nearOverlay.isReady(controllerData);
if (nearOverlayReady.active && HMD.tabletID && nearOverlay.grabbedThingID === HMD.tabletID) { if (nearOverlayReady.active && HMD.tabletID && nearOverlay.grabbedThingID === HMD.tabletID) {
return this.exitModule(); return this.exitModule();
} }

View file

@ -19,12 +19,13 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
function InVREditMode(hand) { function InVREditMode(hand) {
this.hand = hand; this.hand = hand;
this.disableModules = false; this.disableModules = false;
var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed. this.running = false;
var NO_HAND_LASER = -1; // Invalid hand parameter so that standard laser is not displayed.
this.parameters = makeDispatcherModuleParameters( this.parameters = makeDispatcherModuleParameters(
200, // Not too high otherwise the tablet laser doesn't work. 166, // Slightly lower priority than inEditMode.
this.hand === RIGHT_HAND ? this.hand === RIGHT_HAND
["rightHand", "rightHandEquip", "rightHandTrigger"] : ? ["rightHand", "rightHandEquip", "rightHandTrigger"]
["leftHand", "leftHandEquip", "leftHandTrigger"], : ["leftHand", "leftHandEquip", "leftHandTrigger"],
[], [],
100, 100,
makeLaserParams(NO_HAND_LASER, false) makeLaserParams(NO_HAND_LASER, false)
@ -35,6 +36,35 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
(HMD.homeButtonID && objectID === HMD.homeButtonID); (HMD.homeButtonID && objectID === HMD.homeButtonID);
}; };
// The Shapes app has a non-standard laser: in particular, the laser end dot displays on its own when the laser is
// pointing at the Shapes UI. The laser on/off is controlled by this module but the laser is implemented in the Shapes
// app.
// If, in the future, the Shapes app laser interaction is adopted as a standard UI style then the laser could be
// implemented in the controller modules along side the other laser styles.
var INVREDIT_MODULE_RUNNING = "Hifi-InVREdit-Module-Running";
this.runModule = function () {
if (!this.running) {
Messages.sendLocalMessage(INVREDIT_MODULE_RUNNING, JSON.stringify({
hand: this.hand,
running: true
}));
this.running = true;
}
return makeRunningValues(true, [], []);
};
this.exitModule = function () {
if (this.running) {
Messages.sendLocalMessage(INVREDIT_MODULE_RUNNING, JSON.stringify({
hand: this.hand,
running: false
}));
this.running = false;
}
return makeRunningValues(false, [], []);
};
this.isReady = function (controllerData) { this.isReady = function (controllerData) {
if (this.disableModules) { if (this.disableModules) {
return makeRunningValues(true, [], []); return makeRunningValues(true, [], []);
@ -45,7 +75,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
this.run = function (controllerData) { this.run = function (controllerData) {
// Default behavior if disabling is not enabled. // Default behavior if disabling is not enabled.
if (!this.disableModules) { if (!this.disableModules) {
return makeRunningValues(false, [], []); return this.exitModule();
} }
// Tablet stylus. // Tablet stylus.
@ -55,7 +85,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
if (tabletStylusInput) { if (tabletStylusInput) {
var tabletReady = tabletStylusInput.isReady(controllerData); var tabletReady = tabletStylusInput.isReady(controllerData);
if (tabletReady.active) { if (tabletReady.active) {
return makeRunningValues(false, [], []); return this.exitModule();
} }
} }
@ -67,7 +97,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
var overlayLaserReady = overlayLaser.isReady(controllerData); var overlayLaserReady = overlayLaser.isReady(controllerData);
var target = controllerData.rayPicks[this.hand].objectID; var target = controllerData.rayPicks[this.hand].objectID;
if (overlayLaserReady.active && this.pointingAtTablet(target)) { if (overlayLaserReady.active && this.pointingAtTablet(target)) {
return makeRunningValues(false, [], []); return this.exitModule();
} }
} }
@ -78,7 +108,20 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
if (nearOverlay) { if (nearOverlay) {
var nearOverlayReady = nearOverlay.isReady(controllerData); var nearOverlayReady = nearOverlay.isReady(controllerData);
if (nearOverlayReady.active && HMD.tabletID && nearOverlay.grabbedThingID === HMD.tabletID) { if (nearOverlayReady.active && HMD.tabletID && nearOverlay.grabbedThingID === HMD.tabletID) {
return makeRunningValues(false, [], []); return this.exitModule();
}
}
// HUD overlay.
if (!controllerData.triggerClicks[this.hand]) {
var hudLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightHudOverlayPointer"
: "LeftHudOverlayPointer");
if (hudLaser) {
var hudLaserReady = hudLaser.isReady(controllerData);
if (hudLaserReady.active) {
return this.exitModule();
}
} }
} }
@ -89,12 +132,12 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
if (teleporter) { if (teleporter) {
var teleporterReady = teleporter.isReady(controllerData); var teleporterReady = teleporter.isReady(controllerData);
if (teleporterReady.active) { if (teleporterReady.active) {
return makeRunningValues(false, [], []); return this.exitModule();
} }
} }
// Other behaviors are disabled. // Other behaviors are disabled.
return makeRunningValues(true, [], []); return this.runModule();
}; };
} }

View file

@ -1409,7 +1409,6 @@ Script.scriptEnding.connect(function () {
Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE));
Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
Settings.setValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE));
Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE));
Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL));
Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS));
@ -1710,7 +1709,7 @@ function onPromptTextChanged(prompt) {
} }
} }
function handeMenuEvent(menuItem) { function handleMenuEvent(menuItem) {
if (menuItem === "Allow Selecting of Small Models") { if (menuItem === "Allow Selecting of Small Models") {
allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models"); allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models");
} else if (menuItem === "Allow Selecting of Large Models") { } else if (menuItem === "Allow Selecting of Large Models") {
@ -1750,6 +1749,8 @@ function handeMenuEvent(menuItem) {
entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE));
} else if (menuItem === MENU_SHOW_ZONES_IN_EDIT_MODE) { } else if (menuItem === MENU_SHOW_ZONES_IN_EDIT_MODE) {
Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
} else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) {
Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem));
} }
tooltip.show(false); tooltip.show(false);
} }
@ -1875,7 +1876,7 @@ function importSVO(importURL) {
} }
Window.svoImportRequested.connect(importSVO); Window.svoImportRequested.connect(importSVO);
Menu.menuItemEvent.connect(handeMenuEvent); Menu.menuItemEvent.connect(handleMenuEvent);
var keyPressEvent = function (event) { var keyPressEvent = function (event) {
if (isActive) { if (isActive) {

View file

@ -23,12 +23,17 @@
miniState = null, miniState = null,
// Hands. // Hands.
NO_HAND = -1,
LEFT_HAND = 0, LEFT_HAND = 0,
RIGHT_HAND = 1, RIGHT_HAND = 1,
HAND_NAMES = ["LeftHand", "RightHand"], HAND_NAMES = ["LeftHand", "RightHand"],
// Miscellaneous. // Track controller grabbing state.
HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation", HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation",
grabbingHand = NO_HAND,
grabbedItem = null,
// Miscellaneous.
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"), tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"),
DEBUG = false; DEBUG = false;
@ -737,37 +742,11 @@
setState(MINI_VISIBLE); setState(MINI_VISIBLE);
} }
function enterMiniShowing(hand) { function checkMiniVisibility() {
miniHand = hand;
ui.show(miniHand);
miniScaleStart = Date.now();
miniScaleTimer = Script.setTimeout(scaleMiniUp, MINI_SCALE_TIMEOUT);
}
function updateMiniShowing() {
if (HMD.showTablet) {
setState(MINI_HIDDEN);
}
}
function exitMiniShowing() {
if (miniScaleTimer) {
Script.clearTimeout(miniScaleTimer);
miniScaleTimer = null;
}
}
function updateMiniVisible() {
var showLeft, var showLeft,
showRight; showRight;
// Hide mini tablet if tablet proper has been displayed by other means. // Check that the mini tablet should still be visible and if so then ensure it's on the hand that the camera is
if (HMD.showTablet) {
setState(MINI_HIDDEN);
return;
}
// Check that the mini tablet should still be visible and if so then ensure it's on the hand that the camera is
// gazing at. // gazing at.
showLeft = shouldShowMini(LEFT_HAND); showLeft = shouldShowMini(LEFT_HAND);
showRight = shouldShowMini(RIGHT_HAND); showRight = shouldShowMini(RIGHT_HAND);
@ -790,8 +769,47 @@
setState(MINI_HIDING); setState(MINI_HIDING);
} }
} else { } else {
setState(MINI_HIDING); if (grabbedItem === null || grabbingHand !== miniHand) {
setState(MINI_HIDING);
} else {
setState(MINI_HIDDEN);
}
} }
}
function enterMiniShowing(hand) {
miniHand = hand;
ui.show(miniHand);
miniScaleStart = Date.now();
miniScaleTimer = Script.setTimeout(scaleMiniUp, MINI_SCALE_TIMEOUT);
}
function updateMiniShowing() {
// Hide mini tablet if tablet proper has been displayed by other means.
if (HMD.showTablet) {
setState(MINI_HIDDEN);
}
// Hide mini tablet if it should no longer be visible.
checkMiniVisibility();
}
function exitMiniShowing() {
if (miniScaleTimer) {
Script.clearTimeout(miniScaleTimer);
miniScaleTimer = null;
}
}
function updateMiniVisible() {
// Hide mini tablet if tablet proper has been displayed by other means.
if (HMD.showTablet) {
setState(MINI_HIDDEN);
return;
}
// Hide mini tablet if it should no longer be visible.
checkMiniVisibility();
// If state hasn't changed, update mini tablet rotation. // If state hasn't changed, update mini tablet rotation.
if (miniState === MINI_VISIBLE) { if (miniState === MINI_VISIBLE) {
@ -973,6 +991,21 @@
return; return;
} }
// Track grabbed state and item.
switch (message.action) {
case "grab":
grabbingHand = HAND_NAMES.indexOf(message.joint);
grabbedItem = message.grabbedEntity;
break;
case "release":
grabbingHand = NO_HAND;
grabbedItem = null;
break;
default:
error("Unexpected grab message!");
return;
}
if (message.grabbedEntity !== HMD.tabletID && message.grabbedEntity !== ui.getMiniTabletID()) { if (message.grabbedEntity !== HMD.tabletID && message.grabbedEntity !== ui.getMiniTabletID()) {
return; return;
} }

View file

@ -5,14 +5,20 @@
"Oops! Protocol version mismatch.", "Oops! Protocol version mismatch.",
"Oops! Not authorized to join domain.", "Oops! Not authorized to join domain.",
"Oops! Connection timed out.", "Oops! Connection timed out.",
"Oops! The domain is full.",
"Oops! Something went wrong." "Oops! Something went wrong."
]; ];
var PROTOCOL_VERSION_MISMATCH = 1; var PROTOCOL_VERSION_MISMATCH = 1;
var NOT_AUTHORIZED = 3; var NOT_AUTHORIZED = 3;
var DOMAIN_FULL = 4;
var TIMEOUT = 5; var TIMEOUT = 5;
var hardRefusalErrors = [PROTOCOL_VERSION_MISMATCH, var hardRefusalErrors = [
NOT_AUTHORIZED, TIMEOUT]; PROTOCOL_VERSION_MISMATCH,
NOT_AUTHORIZED,
TIMEOUT,
DOMAIN_FULL
];
var timer = null; var timer = null;
var isErrorState = false; var isErrorState = false;
@ -26,7 +32,7 @@
return ERROR_MESSAGE_MAP[errorMessageMapIndex]; return ERROR_MESSAGE_MAP[errorMessageMapIndex];
} else { } else {
// some other text. // some other text.
return ERROR_MESSAGE_MAP[4]; return ERROR_MESSAGE_MAP[ERROR_MESSAGE_MAP.length - 1];
} }
}; };

View file

@ -44,7 +44,10 @@ try {
} }
function removeFromStoryIDsToMaybeDelete(story_id) { function removeFromStoryIDsToMaybeDelete(story_id) {
storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1); story_id = parseInt(story_id);
if (storyIDsToMaybeDelete.indexOf(story_id) > -1) {
storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1);
}
print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete));
} }
@ -258,6 +261,7 @@ function onMessage(message) {
} }
break; break;
case 'removeFromStoryIDsToMaybeDelete': case 'removeFromStoryIDsToMaybeDelete':
console.log("Facebook OR Twitter button clicked for story_id " + message.story_id);
removeFromStoryIDsToMaybeDelete(message.story_id); removeFromStoryIDsToMaybeDelete(message.story_id);
break; break;
default: default:
@ -333,11 +337,13 @@ function fillImageDataFromPrevious() {
var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID"); var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID");
var previousAnimatedSnapBlastingDisabled = Settings.getValue("previousAnimatedSnapBlastingDisabled"); var previousAnimatedSnapBlastingDisabled = Settings.getValue("previousAnimatedSnapBlastingDisabled");
var previousAnimatedSnapHifiSharingDisabled = Settings.getValue("previousAnimatedSnapHifiSharingDisabled"); var previousAnimatedSnapHifiSharingDisabled = Settings.getValue("previousAnimatedSnapHifiSharingDisabled");
snapshotOptions = { snapshotOptions = {
containsGif: previousAnimatedSnapPath !== "", containsGif: previousAnimatedSnapPath !== "",
processingGif: false, processingGif: false,
shouldUpload: false, shouldUpload: false,
canBlast: snapshotDomainID === Settings.getValue("previousSnapshotDomainID"), canBlast: snapshotDomainID === Settings.getValue("previousSnapshotDomainID") &&
snapshotDomainID === location.domainID,
isLoggedIn: isLoggedIn isLoggedIn: isLoggedIn
}; };
imageData = []; imageData = [];
@ -369,7 +375,8 @@ function snapshotUploaded(isError, reply) {
isGif = fileExtensionMatches(imageURL, "gif"), isGif = fileExtensionMatches(imageURL, "gif"),
ignoreGifSnapshotData = false, ignoreGifSnapshotData = false,
ignoreStillSnapshotData = false; ignoreStillSnapshotData = false;
storyIDsToMaybeDelete.push(storyID); storyIDsToMaybeDelete.push(parseInt(storyID));
print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete));
if (isGif) { if (isGif) {
if (mostRecentGifSnapshotFilename !== replyJson.user_story.details.original_image_file_name) { if (mostRecentGifSnapshotFilename !== replyJson.user_story.details.original_image_file_name) {
ignoreGifSnapshotData = true; ignoreGifSnapshotData = true;

View file

@ -338,13 +338,15 @@ const HifiNotificationType = hfNotifications.NotificationType;
var pendingNotifications = {} var pendingNotifications = {}
var notificationState = NotificationState.UNNOTIFIED; var notificationState = NotificationState.UNNOTIFIED;
function setNotificationState (notificationType, pending = true) { function setNotificationState (notificationType, pending = undefined) {
pendingNotifications[notificationType] = pending; if (pending !== undefined) {
notificationState = NotificationState.UNNOTIFIED; pendingNotifications[notificationType] = pending;
for (var key in pendingNotifications) { notificationState = NotificationState.UNNOTIFIED;
if (pendingNotifications[key]) { for (var key in pendingNotifications) {
notificationState = NotificationState.NOTIFIED; if (pendingNotifications[key]) {
break; notificationState = NotificationState.NOTIFIED;
break;
}
} }
} }
updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED); updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED);
@ -568,7 +570,42 @@ function updateLabels(serverState) {
labels.people.icon = pendingNotifications[HifiNotificationType.PEOPLE] ? menuNotificationIcon : null; labels.people.icon = pendingNotifications[HifiNotificationType.PEOPLE] ? menuNotificationIcon : null;
labels.wallet.icon = pendingNotifications[HifiNotificationType.WALLET] ? menuNotificationIcon : null; labels.wallet.icon = pendingNotifications[HifiNotificationType.WALLET] ? menuNotificationIcon : null;
labels.marketplace.icon = pendingNotifications[HifiNotificationType.MARKETPLACE] ? menuNotificationIcon : null; labels.marketplace.icon = pendingNotifications[HifiNotificationType.MARKETPLACE] ? menuNotificationIcon : null;
var onlineUsers = trayNotifications.getOnlineUsers();
delete labels.people.submenu;
if (onlineUsers) {
for (var name in onlineUsers) {
if(labels.people.submenu == undefined) {
labels.people.submenu = [];
}
labels.people.submenu.push({
label: name,
enabled: (onlineUsers[name].location != undefined),
click: function (item) {
setNotificationState(HifiNotificationType.PEOPLE, false);
if(onlineUsers[item.label] && onlineUsers[item.label].location) {
StartInterface("hifi://" + onlineUsers[item.label].location.root.name + onlineUsers[item.label].location.path);
}
}
});
}
}
var currentStories = trayNotifications.getCurrentStories();
delete labels.goto.submenu;
if (currentStories) {
for (var location in currentStories) {
if(labels.goto.submenu == undefined) {
labels.goto.submenu = [];
}
labels.goto.submenu.push({
label: "event in " + location,
location: location,
click: function (item) {
setNotificationState(HifiNotificationType.GOTO, false);
StartInterface("hifi://" + item.location + currentStories[item.location].path);
}
});
}
}
} }
function updateTrayMenu(serverState) { function updateTrayMenu(serverState) {
@ -919,6 +956,8 @@ app.on('ready', function() {
trayNotifications.startPolling(); trayNotifications.startPolling();
} }
updateTrayMenu(ProcessGroupStates.STOPPED); updateTrayMenu(ProcessGroupStates.STOPPED);
maybeInstallDefaultContentSet(onContentLoaded); if (isServerInstalled()) {
maybeInstallDefaultContentSet(onContentLoaded);
}
}); });

View file

@ -73,11 +73,17 @@ HifiNotification.prototype = {
text = this.data + " of your connections are online." text = this.data + " of your connections are online."
} }
message = "Click to open PEOPLE."; message = "Click to open PEOPLE.";
url="hifiapp:PEOPLE" url="hifiapp:PEOPLE";
} else { } else {
text = this.data.username + " is available in " + this.data.location.root.name + "."; if (this.data.location) {
message = "Click to join them."; text = this.data.username + " is available in " + this.data.location.root.name + ".";
url="hifi://" + this.data.location.root.name + this.data.location.path; message = "Click to join them.";
url="hifi://" + this.data.location.root.name + this.data.location.path;
} else {
text = this.data.username + " is online.";
message = "Click to open PEOPLE.";
url="hifiapp:PEOPLE";
}
} }
break; break;
@ -136,7 +142,8 @@ HifiNotification.prototype = {
function HifiNotifications(config, menuNotificationCallback) { function HifiNotifications(config, menuNotificationCallback) {
this.config = config; this.config = config;
this.menuNotificationCallback = menuNotificationCallback; this.menuNotificationCallback = menuNotificationCallback;
this.onlineUsers = new Set([]); this.onlineUsers = {};
this.currentStories = {};
this.storiesSince = new Date(this.config.get("storiesNotifySince", "1970-01-01T00:00:00.000Z")); this.storiesSince = new Date(this.config.get("storiesNotifySince", "1970-01-01T00:00:00.000Z"));
this.peopleSince = new Date(this.config.get("peopleNotifySince", "1970-01-01T00:00:00.000Z")); this.peopleSince = new Date(this.config.get("peopleNotifySince", "1970-01-01T00:00:00.000Z"));
this.walletSince = new Date(this.config.get("walletNotifySince", "1970-01-01T00:00:00.000Z")); this.walletSince = new Date(this.config.get("walletNotifySince", "1970-01-01T00:00:00.000Z"));
@ -213,6 +220,12 @@ HifiNotifications.prototype = {
clearInterval(this.marketplacePollTimer); clearInterval(this.marketplacePollTimer);
} }
}, },
getOnlineUsers: function () {
return this.onlineUsers;
},
getCurrentStories: function () {
return this.currentStories;
},
_showNotification: function () { _showNotification: function () {
var _this = this; var _this = this;
@ -225,7 +238,7 @@ HifiNotifications.prototype = {
// previous notification immediately when a // previous notification immediately when a
// new one is submitted // new one is submitted
_this.pendingNotifications.shift(); _this.pendingNotifications.shift();
if(_this.pendingNotifications.length > 0) { if (_this.pendingNotifications.length > 0) {
_this._showNotification(); _this._showNotification();
} }
}); });
@ -289,7 +302,6 @@ HifiNotifications.prototype = {
finished(false); finished(false);
return; return;
} }
console.log(content);
if (!content.total_entries) { if (!content.total_entries) {
finished(true, token); finished(true, token);
return; return;
@ -313,7 +325,6 @@ HifiNotifications.prototype = {
notifyData = content.data.updates; notifyData = content.data.updates;
break; break;
} }
notifyData.forEach(function (notifyDataEntry) { notifyData.forEach(function (notifyDataEntry) {
_this._addNotification(new HifiNotification(notifyType, notifyDataEntry)); _this._addNotification(new HifiNotification(notifyType, notifyDataEntry));
}); });
@ -346,7 +357,7 @@ HifiNotifications.prototype = {
'include_actions=announcement', 'include_actions=announcement',
'restriction=open,hifi', 'restriction=open,hifi',
'require_online=true', 'require_online=true',
'per_page=1' 'per_page=' + MAX_NOTIFICATION_ITEMS
]; ];
var url = METAVERSE_SERVER_URL + STORIES_URL + '?' + options.join('&'); var url = METAVERSE_SERVER_URL + STORIES_URL + '?' + options.join('&');
// call a second time to determine if there are no more stories and we should // call a second time to determine if there are no more stories and we should
@ -357,7 +368,34 @@ HifiNotifications.prototype = {
'bearer': token 'bearer': token
} }
}, function (error, data) { }, function (error, data) {
_this._pollToDisableHighlight(NotificationType.GOTO, error, data); if (error || !data.body) {
console.log("Error: unable to get " + url);
finished(false);
return;
}
var content = JSON.parse(data.body);
if (!content || content.status != 'success') {
console.log("Error: unable to get " + url);
finished(false);
return;
}
if (!content.total_entries) {
finished(true, token);
return;
}
if (!content.total_entries) {
_this.menuNotificationCallback(NotificationType.GOTO, false);
}
_this.currentStories = {};
content.user_stories.forEach(function (story) {
// only show a single instance of each story location
// in the menu. This may cause issues with domains
// where the story locations are significantly different
// for each story.
_this.currentStories[story.place_name] = story;
});
_this.menuNotificationCallback(NotificationType.GOTO);
}); });
} }
}); });
@ -400,20 +438,19 @@ HifiNotifications.prototype = {
console.log("Error: unable to get " + url); console.log("Error: unable to get " + url);
return false; return false;
} }
console.log(content);
if (!content.total_entries) { if (!content.total_entries) {
_this.menuNotificationCallback(NotificationType.PEOPLE, false); _this.menuNotificationCallback(NotificationType.PEOPLE, false);
_this.onlineUsers = new Set([]); _this.onlineUsers = {};
return; return;
} }
var currentUsers = new Set([]); var currentUsers = {};
var newUsers = new Set([]); var newUsers = new Set([]);
content.data.users.forEach(function (user) { content.data.users.forEach(function (user) {
currentUsers.add(user.username); currentUsers[user.username] = user;
if (!_this.onlineUsers.has(user.username)) { if (!(user.username in _this.onlineUsers)) {
newUsers.add(user); newUsers.add(user);
_this.onlineUsers.add(user.username); _this.onlineUsers[user.username] = user;
} }
}); });
_this.onlineUsers = currentUsers; _this.onlineUsers = currentUsers;