diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 990d84a774..cc9fe8ef4c 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -332,6 +332,10 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources/fonts" "${RESOURCES_DEV_DIR}/fonts" + # add redirect json to macOS builds. + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${PROJECT_SOURCE_DIR}/resources/serverless/redirect.json" + "${RESOURCES_DEV_DIR}/serverless/redirect.json" ) # call the fixup_interface macro to add required bundling commands for installation @@ -360,6 +364,9 @@ else() COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${PROJECT_SOURCE_DIR}/resources/serverless/tutorial.json" "${RESOURCES_DEV_DIR}/serverless/tutorial.json" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${PROJECT_SOURCE_DIR}/resources/serverless/redirect.json" + "${RESOURCES_DEV_DIR}/serverless/redirect.json" # copy JSDoc files beside the executable COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/tools/jsdoc/out" 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/resources/meshes/redirect/oopsDialog_auth.fbx b/interface/resources/meshes/redirect/oopsDialog_auth.fbx new file mode 100644 index 0000000000..3360de993f Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_auth.fbx differ diff --git a/interface/resources/meshes/redirect/oopsDialog_auth.png b/interface/resources/meshes/redirect/oopsDialog_auth.png new file mode 100644 index 0000000000..2165cfbfa9 Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_auth.png differ diff --git a/interface/resources/meshes/redirect/oopsDialog_protocol.fbx b/interface/resources/meshes/redirect/oopsDialog_protocol.fbx new file mode 100644 index 0000000000..794a5f2dfd Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_protocol.fbx differ diff --git a/interface/resources/meshes/redirect/oopsDialog_protocol.png b/interface/resources/meshes/redirect/oopsDialog_protocol.png new file mode 100644 index 0000000000..63c6e00c4e Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_protocol.png differ diff --git a/interface/resources/meshes/redirect/oopsDialog_vague.fbx b/interface/resources/meshes/redirect/oopsDialog_vague.fbx new file mode 100644 index 0000000000..324d90578b Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_vague.fbx differ diff --git a/interface/resources/meshes/redirect/oopsDialog_vague.png b/interface/resources/meshes/redirect/oopsDialog_vague.png new file mode 100644 index 0000000000..8a02ad2c27 Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_vague.png differ diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 802c00a97a..3d518289fb 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -79,7 +79,7 @@ StackView { return; } location.text = targetString; - toggleOrGo(true, targetString); + toggleOrGo(targetString, true); clearAddressLineTimer.start(); } @@ -399,7 +399,7 @@ StackView { } } - function toggleOrGo(fromSuggestions, address) { + function toggleOrGo(address, fromSuggestions) { if (address !== undefined && address !== "") { addressBarDialog.loadAddress(address, fromSuggestions); clearAddressLineTimer.start(); diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json new file mode 100644 index 0000000000..64cb4d8a3f --- /dev/null +++ b/interface/resources/serverless/redirect.json @@ -0,0 +1,934 @@ +{ + "DataVersion": 0, + "Paths": + { + "/": "/4,1.4,4/0,0.49544,0,0.868645" + }, + "Entities": [ + { + "clientOnly": false, + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "created": "2018-09-05T18:13:00Z", + "dimensions": { + "blue": 1.159199833869934, + "green": 2.8062009811401367, + "red": 1.6216505765914917, + "x": 1.6216505765914917, + "y": 2.8062009811401367, + "z": 1.159199833869934 + }, + "id": "{d0ed60b8-9174-4c56-8e78-2c5399329ae0}", + "lastEdited": 1536171372916208, + "lastEditedBy": "{151cb20e-715a-4c80-aa0d-5b58b1c8a0c9}", + "locked": true, + "name": "Try Again Zone", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue":4.015342712402344, + "green":1.649999976158142, + "red":2.00921893119812, + "x":2.00921893119812, + "y":1.649999976158142, + "z":4.015342712402344 + }, + "queryAACube": { + "scale": 3.4421300888061523, + "x": 1.6001315116882324, + "y": -0.07100248336791992, + "z": 0.14220571517944336 + }, + "rotation": { + "w": 0.9914448857307434, + "x": 0, + "y": -0.13052619993686676, + "z": 0 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/zoneTryAgainEntityScript.js", + "shapeType": "box", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 8.645400047302246, + "green": 0.20000000298023224, + "red": 20.025121688842773, + "x": 20.025121688842773, + "y": 0.20000000298023224, + "z": 8.645400047302246 + }, + "id": "{e44fb546-b34a-4966-9b11-73556f800d21}", + "lastEdited": 1536107948776951, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "ceiling", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 4.846520900726318, + "green": 2.912982940673828, + "red": 5.739595890045166, + "x": 5.739595890045166, + "y": 2.912982940673828, + "z": 4.846520900726318 + }, + "queryAACube": { + "scale": 21.812576293945312, + "x": -5.16669225692749, + "y": -7.993305206298828, + "z": -6.059767246246338 + }, + "rotation": { + "w": 0.970295786857605, + "x": 0, + "y": -0.24192190170288086, + "z": 0 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 6.9401350021362305, + "green": 0.04553089290857315, + "red": 7.004304885864258, + "x": 7.004304885864258, + "y": 0.04553089290857315, + "z": 6.9401350021362305 + }, + "id": "{8cd93fe5-16c0-44b7-b1e9-e7e06c4e9228}", + "lastEdited": 1536107948774796, + "lastEditedBy": "{4eecd88f-ef9b-4a83-bb9a-7f7496209c6b}", + "locked": true, + "name": "floor", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.6175529956817627, + "green": 0, + "red": 4.102385997772217, + "x": 4.102385997772217, + "y": 0, + "z": 3.6175529956817627 + }, + "queryAACube": { + "scale": 9.860417366027832, + "x": -0.8278226852416992, + "y": -4.930208683013916, + "z": -1.3126556873321533 + }, + "rotation": { + "w": 0.8660253882408142, + "x": -1.5922749298624694e-05, + "y": 0.5, + "z": -4.572480611386709e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{147272dc-a344-4171-9621-efc1c2095997}", + "lastEdited": 1536107948776823, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "leftWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 6.1806135177612305, + "green": 1.0066027641296387, + "red": 1.4690406322479248, + "x": 1.4690406322479248, + "y": 1.0066027641296387, + "z": 6.1806135177612305 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -4.371703147888184, + "y": -4.834141254425049, + "z": 0.33986949920654297 + }, + "rotation": { + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{5f2b89b8-47e3-4915-a966-d46307a40f06}", + "lastEdited": 1536107948774605, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "backWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 5.268576622009277, + "green": 1.0066027641296387, + "red": 6.093774318695068, + "x": 6.093774318695068, + "y": 1.0066027641296387, + "z": 5.268576622009277 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": 0.25303030014038086, + "y": -4.834141254425049, + "z": -0.5721673965454102 + }, + "rotation": { + "w": 0.9662165641784668, + "x": -4.57763671875e-05, + "y": -0.2576791048049927, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 14.40000057220459, + "green": 14.40000057220459, + "red": 14.40000057220459, + "x": 14.40000057220459, + "y": 14.40000057220459, + "z": 14.40000057220459 + }, + "id": "{baf96345-8f68-4068-af4c-3c690035852a}", + "lastEdited": 1536107948775591, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 2.3440732955932617, + "green": 1.6162219047546387, + "red": 1.8748211860656738, + "x": 1.8748211860656738, + "y": 1.6162219047546387, + "z": 2.3440732955932617 + }, + "queryAACube": { + "scale": 24.9415340423584, + "x": -10.595945358276367, + "y": -10.854545593261719, + "z": -10.126693725585938 + }, + "rotation": { + "w": 0.8697794675827026, + "x": -1.52587890625e-05, + "y": 0.4933699369430542, + "z": -4.57763671875e-05 + }, + "shapeType": "box", + "skyboxMode": "enabled", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 1, + "clientOnly": false, + "color": { + "blue": 211, + "green": 227, + "red": 104 + }, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 211, + "green": 227, + "red": 104, + "x": 104, + "y": 227, + "z": 211 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.5, + "green": 2.5, + "red": 2.5, + "x": 2.5, + "y": 2.5, + "z": 2.5 + }, + "emitAcceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 0.9993909597396851, + "x": 0.034897372126579285, + "y": -1.525880907138344e-05, + "z": -1.525880907138344e-05 + }, + "emitRate": 2, + "emitSpeed": 0, + "id": "{639a51f0-8613-4e46-bc7e-fef24597df73}", + "lastEdited": 1536107948776693, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 40, + "name": "Rays", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.75, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 1.3553659915924072, + "green": 1.2890124320983887, + "red": 2.5663273334503174, + "x": 2.5663273334503174, + "y": 1.2890124320983887, + "z": 1.3553659915924072 + }, + "queryAACube": { + "scale": 4.330127239227295, + "x": 0.4012637138366699, + "y": -0.8760511875152588, + "z": -0.8096976280212402 + }, + "radiusFinish": 0.10000000149011612, + "radiusStart": 0, + "rotation": { + "w": 0.9803768396377563, + "x": -1.52587890625e-05, + "y": 0.19707024097442627, + "z": -7.62939453125e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 1, + "clientOnly": false, + "color": { + "blue": 255, + "green": 205, + "red": 3 + }, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 204, + "red": 0, + "x": 0, + "y": 204, + "z": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.5, + "green": 2.5, + "red": 2.5, + "x": 2.5, + "y": 2.5, + "z": 2.5 + }, + "emitAcceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 0.9993909597396851, + "x": 0.034897372126579285, + "y": -1.525880907138344e-05, + "z": -1.525880907138344e-05 + }, + "emitRate": 2, + "emitSpeed": 0, + "emitterShouldTrail": true, + "id": "{e62ced49-fa18-4ae1-977f-abef5bc0f3ba}", + "lastEdited": 1536107948775366, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 40, + "name": "Rays", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.75, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.814434051513672, + "green": 1.2890124320983887, + "red": 1.2254328727722168, + "x": 1.2254328727722168, + "y": 1.2890124320983887, + "z": 3.814434051513672 + }, + "queryAACube": { + "scale": 4.330127239227295, + "x": -0.9396307468414307, + "y": -0.8760511875152588, + "z": 1.6493704319000244 + }, + "radiusFinish": 0.10000000149011612, + "radiusStart": 0, + "rotation": { + "w": 0.9594720602035522, + "x": -1.52587890625e-05, + "y": 0.28178834915161133, + "z": -4.57763671875e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "id": "{298c0571-cbd8-487b-8640-64037d6a8414}", + "lastEdited": 1536107948776382, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 1.3712034225463867, + "green": 0.3698839843273163, + "red": 2.6216418743133545, + "x": 2.6216418743133545, + "y": 0.3698839843273163, + "z": 1.3712034225463867 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -8.844534873962402, + "y": -11.096293449401855, + "z": -10.09497356414795 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.9852597713470459, + "x": -1.52587890625e-05, + "y": -0.17106890678405762, + "z": -7.62939453125e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{6625dbb8-ff25-458d-a92e-644b58460604}", + "lastEdited": 1536107948776195, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal1.fbx", + "name": "Try Again", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.946338653564453, + "green": 0.09449335932731628, + "red": 1.594836711883545, + "x": 1.594836711883545, + "y": 0.09449335932731628, + "z": 3.946338653564453 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 0.3122459650039673, + "y": -1.188097357749939, + "z": 2.663747787475586 + }, + "rotation": { + "w": 0.8220492601394653, + "x": -1.52587890625e-05, + "y": 0.5693598985671997, + "z": -0.0001068115234375 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/tryAgainEntityScript.js", + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 0.06014331430196762, + "green": 2.582186460494995, + "red": 2.582186698913574, + "x": 2.582186698913574, + "y": 2.582186460494995, + "z": 0.06014331430196762 + }, + "id": "{dfe92dce-f09d-4e9e-b3ed-c68ecd4d476f}", + "lastEdited": 1536108160862286, + "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", + "modelURL": "", + "name": "Oops Dialog", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.45927095413208, + "green": 1.6763916015625, + "red": 0, + "x": 0, + "y": 1.6763916015625, + "z": 1.45927095413208 + }, + "queryAACube": { + "scale": 3.6522583961486816, + "x": -1.8261291980743408, + "y": -0.14973759651184082, + "z": -0.36685824394226074 + }, + "rotation": { + "w": 0.8684672117233276, + "x": -4.57763671875e-05, + "y": 0.4957197904586792, + "z": -7.62939453125e-05 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/oopsEntityScript.js", + "scriptTimestamp": 1536102551825, + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{144a8cf4-b0e8-489a-9403-d74d4dc4cb3e}", + "lastEdited": 1536107948775774, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "rightWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 0, + "green": 1.0061144828796387, + "red": 4.965089321136475, + "x": 4.965089321136475, + "y": 1.0061144828796387, + "z": 0 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -0.8756546974182129, + "y": -4.834629535675049, + "z": -5.8407440185546875 + }, + "rotation": { + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 1.159199833869934, + "green": 2.8062009811401367, + "red": 1.6216505765914917, + "x": 1.6216505765914917, + "y": 2.8062009811401367, + "z": 1.159199833869934 + }, + "id": "{37f53408-3d0c-42a5-9891-e6c40a227349}", + "lastEdited": 1536107948775010, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "name": "Back Zone", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.8632707595825195, + "green": 1.6500625610351562, + "red": 3.3211965560913086, + "x": 3.3211965560913086, + "y": 1.6500625610351562, + "z": 1.8632707595825195 + }, + "queryAACube": { + "scale": 3.4421300888061523, + "x": 1.6001315116882324, + "y": -0.07100248336791992, + "z": 0.14220571517944336 + }, + "rotation": { + "w": 0.9304176568984985, + "x": 0, + "y": -0.36650121212005615, + "z": 0 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/zoneBackEntityScript.js", + "shapeType": "box", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{aa6e680c-6750-4776-95bc-ef3118cace5c}", + "lastEdited": 1536107948775945, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "frontWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 2.662257671356201, + "green": 1.0063786506652832, + "red": 1.4868733882904053, + "x": 1.4868733882904053, + "y": 1.0063786506652832, + "z": 2.662257671356201 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -4.353870391845703, + "y": -4.834365367889404, + "z": -3.1784863471984863 + }, + "rotation": { + "w": 0.9666743278503418, + "x": -4.57763671875e-05, + "y": -0.2560006380081177, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{303631f1-04f3-42a6-b8a8-8dd4b65d1231}", + "lastEdited": 1536107948776513, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal2.fbx", + "name": "Back", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.5835940837860107, + "green": 0.09449335932731628, + "red": 3.028078079223633, + "x": 3.028078079223633, + "y": 0.09449335932731628, + "z": 1.5835940837860107 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 1.7454873323440552, + "y": -1.188097357749939, + "z": 0.3010033369064331 + }, + "rotation": { + "w": 0.9084458351135254, + "x": -1.52587890625e-05, + "y": 0.4179598093032837, + "z": -0.0001068115234375 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/backEntityScript.js", + "scriptTimestamp": 1535751754379, + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "emitterShouldTrail": true, + "id": "{8ded39e6-303c-48f2-be79-81b715cca9f7}", + "lastEdited": 1536107948777127, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.78922963142395, + "green": 0.3698839843273163, + "red": 1.1863799095153809, + "x": 1.1863799095153809, + "y": 0.3698839843273163, + "z": 3.78922963142395 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -10.279796600341797, + "y": -11.096293449401855, + "z": -7.676947593688965 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.996429443359375, + "x": -1.52587890625e-05, + "y": -0.08442819118499756, + "z": -4.57763671875e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + } + ], + "Id": "{18abccad-2d57-4176-9d89-24dc424916f5}", + "Version": 93 +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 468bb31ff3..77e2cb3211 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1186,6 +1186,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainURLChanged(QUrl))); + connect(&domainHandler, SIGNAL(redirectToErrorDomainURL(QUrl)), SLOT(goToErrorDomainURL(QUrl))); connect(&domainHandler, &DomainHandler::domainURLChanged, [](QUrl domainURL){ setCrashAnnotation("domain", domainURL.toString().toStdString()); }); @@ -1199,6 +1200,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); + nodeList->getDomainHandler().setErrorDomainURL(QUrl(REDIRECT_HIFI_ADDRESS)); + // We could clear ATP assets only when changing domains, but it's possible that the domain you are connected // to has gone down and switched to a new content set, so when you reconnect the cached ATP assets will no longer be valid. connect(&domainHandler, &DomainHandler::disconnectedFromDomain, DependencyManager::get().data(), &ScriptCache::clearATPScriptsFromCache); @@ -1640,7 +1643,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(); @@ -2249,6 +2252,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose); connect(&domainHandler, &DomainHandler::domainURLChanged, this, &Application::addAssetToWorldMessageClose); + connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &Application::addAssetToWorldMessageClose); updateSystemTabletMode(); @@ -3012,6 +3016,9 @@ void Application::initializeUi() { if (_window && _window->isFullScreen()) { setFullscreen(nullptr, true); } + + + setIsInterstitialMode(true); } @@ -3490,6 +3497,15 @@ bool Application::isServerlessMode() const { return false; } +void Application::setIsInterstitialMode(bool interstitialMode) { + if (_interstitialMode != interstitialMode) { + _interstitialMode = interstitialMode; + + DependencyManager::get()->setAudioPaused(_interstitialMode); + DependencyManager::get()->setMyAvatarDataPacketsPaused(_interstitialMode); + } +} + void Application::setIsServerlessMode(bool serverlessDomain) { auto tree = getEntities()->getTree(); if (tree) { @@ -3497,9 +3513,9 @@ void Application::setIsServerlessMode(bool serverlessDomain) { } } -void Application::loadServerlessDomain(QUrl domainURL) { +void Application::loadServerlessDomain(QUrl domainURL, bool errorDomain) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL)); + QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL), Q_ARG(bool, errorDomain)); return; } @@ -3531,8 +3547,11 @@ void Application::loadServerlessDomain(QUrl domainURL) { } std::map namedPaths = tmpTree->getNamedPaths(); - nodeList->getDomainHandler().connectedToServerless(namedPaths); - + if (errorDomain) { + nodeList->getDomainHandler().loadedErrorDomain(namedPaths); + } else { + nodeList->getDomainHandler().connectedToServerless(namedPaths); + } _fullSceneReceivedCounter++; } @@ -3767,7 +3786,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; } @@ -5554,6 +5573,7 @@ void Application::update(float deltaTime) { return; } + if (!_physicsEnabled) { if (!domainLoadingInProgress) { PROFILE_ASYNC_BEGIN(app, "Scene Loading", ""); @@ -5574,6 +5594,7 @@ void Application::update(float deltaTime) { // scene is ready to compute its collision shape. if (getMyAvatar()->isReadyForPhysics()) { _physicsEnabled = true; + setIsInterstitialMode(false); getMyAvatar()->updateMotionBehaviorFromMenu(); } } @@ -5653,7 +5674,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)); @@ -5969,7 +5990,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); @@ -6132,21 +6153,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); } @@ -6369,6 +6392,7 @@ void Application::clearDomainOctreeDetails() { qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); + setIsInterstitialMode(true); _octreeServerSceneStats.withWriteLock([&] { _octreeServerSceneStats.clear(); @@ -6399,6 +6423,16 @@ void Application::domainURLChanged(QUrl domainURL) { updateWindowTitle(); } +void Application::goToErrorDomainURL(QUrl errorDomainURL) { + // disable physics until we have enough information about our new location to not cause craziness. + resetPhysicsReadyInformation(); + setIsServerlessMode(errorDomainURL.scheme() != URL_SCHEME_HIFI); + if (isServerlessMode()) { + loadServerlessDomain(errorDomainURL, true); + } + updateWindowTitle(); +} + void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; @@ -6437,7 +6471,7 @@ void Application::nodeActivated(SharedNodePointer node) { _octreeQuery.incrementConnectionID(); } - if (node->getType() == NodeType::AudioMixer) { + if (node->getType() == NodeType::AudioMixer && !isInterstitialMode()) { DependencyManager::get()->negotiateAudioFormat(); } @@ -6457,8 +6491,10 @@ void Application::nodeActivated(SharedNodePointer node) { getMyAvatar()->markIdentityDataChanged(); getMyAvatar()->resetLastSent(); - // transmit a "sendAll" packet to the AvatarMixer we just connected to. - getMyAvatar()->sendAvatarDataPacket(true); + if (!isInterstitialMode()) { + // transmit a "sendAll" packet to the AvatarMixer we just connected to. + getMyAvatar()->sendAvatarDataPacket(true); + } } } @@ -7809,7 +7845,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 2bc679e9d6..3bebc60480 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -224,6 +224,7 @@ public: void setHmdTabletBecomesToolbarSetting(bool value); bool getPreferStylusOverLaser() { return _preferStylusOverLaserSetting.get(); } void setPreferStylusOverLaser(bool value); + // FIXME: Remove setting completely or make available through JavaScript API? //bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); } bool getPreferAvatarFingerOverStylus() { return false; } @@ -304,6 +305,7 @@ public: void saveNextPhysicsStats(QString filename); bool isServerlessMode() const; + bool isInterstitialMode() const { return _interstitialMode; } void replaceDomainContent(const QString& url); @@ -340,6 +342,7 @@ public slots: bool importEntities(const QString& url); void updateThreadPoolCount() const; void updateSystemTabletMode(); + void goToErrorDomainURL(QUrl errorDomainURL); Q_INVOKABLE void loadDialog(); Q_INVOKABLE void loadScriptURLDialog() const; @@ -428,7 +431,8 @@ public slots: void setPreferredCursor(const QString& cursor); void setIsServerlessMode(bool serverlessDomain); - void loadServerlessDomain(QUrl domainURL); + void loadServerlessDomain(QUrl domainURL, bool errorDomain = false); + void setIsInterstitialMode(bool interstialMode); void updateVerboseLogging(); @@ -627,6 +631,7 @@ private: QHash _keysPressed; bool _enableProcessOctreeThread; + bool _interstitialMode { false }; OctreePacketProcessor _octreeProcessor; EntityEditPacketSender _entityEditSender; diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 8deddbda82..3c85cfb339 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -11,16 +11,18 @@ #include "ConnectionMonitor.h" +#include "Application.h" #include "ui/DialogsManager.h" #include #include +#include #include // Because the connection monitor is created at startup, the time we wait on initial load // should be longer to allow the application to initialize. -static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000; -static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000; +static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000; +static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000; void ConnectionMonitor::init() { // Connect to domain disconnected message @@ -30,23 +32,25 @@ void ConnectionMonitor::init() { connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &ConnectionMonitor::startTimer); connect(&domainHandler, &DomainHandler::connectedToDomain, this, &ConnectionMonitor::stopTimer); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ConnectionMonitor::stopTimer); + connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &ConnectionMonitor::stopTimer); + connect(this, &ConnectionMonitor::setRedirectErrorState, &domainHandler, &DomainHandler::setRedirectErrorState); _timer.setSingleShot(true); if (!domainHandler.isConnected()) { - _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); + _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } - connect(&_timer, &QTimer::timeout, this, []() { - qDebug() << "ConnectionMonitor: Showing connection failure window"; - DependencyManager::get()->setDomainConnectionFailureVisibility(true); + connect(&_timer, &QTimer::timeout, this, [this]() { + qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; + // set in a timeout error + emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); }); } void ConnectionMonitor::startTimer() { - _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); + _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } void ConnectionMonitor::stopTimer() { _timer.stop(); - DependencyManager::get()->setDomainConnectionFailureVisibility(false); } diff --git a/interface/src/ConnectionMonitor.h b/interface/src/ConnectionMonitor.h index e3d393163b..5e75e2618b 100644 --- a/interface/src/ConnectionMonitor.h +++ b/interface/src/ConnectionMonitor.h @@ -15,6 +15,7 @@ #include #include +class QUrl; class QString; class ConnectionMonitor : public QObject { @@ -22,6 +23,9 @@ class ConnectionMonitor : public QObject { public: void init(); +signals: + void setRedirectErrorState(QUrl errorURL, int reasonCode); + private slots: void startTimer(); void stopTimer(); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index e9486b9def..b9e30a38eb 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 && !_myAvatarDataPacketsPaused) { // send head/hand data to the avatar mixer and voxel server PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); @@ -155,6 +155,16 @@ float AvatarManager::getAvatarDataRate(const QUuid& sessionID, const QString& ra return avatar ? avatar->getDataRate(rateName) : 0.0f; } +void AvatarManager::setMyAvatarDataPacketsPaused(bool pause) { + if (_myAvatarDataPacketsPaused != pause) { + _myAvatarDataPacketsPaused = pause; + + if (!_myAvatarDataPacketsPaused) { + _myAvatar->sendAvatarDataPacket(true); + } + } +} + float AvatarManager::getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName) const { auto avatar = getAvatarBySessionID(sessionID); return avatar ? avatar->getUpdateRate(rateName) : 0.0f; @@ -802,13 +812,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/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 7e1e1f4ff1..306ba6f39b 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -91,6 +91,8 @@ public: void updateOtherAvatars(float deltaTime); void sendIdentityRequest(const QUuid& avatarID) const; + void setMyAvatarDataPacketsPaused(bool puase); + void postUpdate(float deltaTime, const render::ScenePointer& scene); void clearOtherAvatars() override; @@ -219,6 +221,7 @@ private: int _numAvatarsNotUpdated { 0 }; float _avatarSimulationTime { 0.0f }; bool _shouldRender { true }; + bool _myAvatarDataPacketsPaused { false }; mutable int _identityRequestsSent { 0 }; mutable std::mutex _spaceLock; diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 4bc6817a9e..0d991fc9bc 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -16,7 +16,6 @@ #include "Application.h" #include "Menu.h" #include "SceneScriptingInterface.h" -#include "SafeLanding.h" OctreePacketProcessor::OctreePacketProcessor(): _safeLanding(new SafeLanding()) @@ -133,7 +132,3 @@ void OctreePacketProcessor::processPacket(QSharedPointer messag void OctreePacketProcessor::startEntitySequence() { _safeLanding->startEntitySequence(qApp->getEntities()); } - -bool OctreePacketProcessor::isLoadSequenceComplete() const { - return _safeLanding->isLoadSequenceComplete(); -} diff --git a/interface/src/octree/OctreePacketProcessor.h b/interface/src/octree/OctreePacketProcessor.h index f9c24ddc51..d6ffb942e6 100644 --- a/interface/src/octree/OctreePacketProcessor.h +++ b/interface/src/octree/OctreePacketProcessor.h @@ -15,7 +15,7 @@ #include #include -class SafeLanding; +#include "SafeLanding.h" /// Handles processing of incoming voxel packets for the interface application. As with other ReceivedPacketProcessor classes /// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket() @@ -26,7 +26,8 @@ public: ~OctreePacketProcessor(); void startEntitySequence(); - bool isLoadSequenceComplete() const; + bool isLoadSequenceComplete() const { return _safeLanding->isLoadSequenceComplete(); } + float domainLoadingProgress() const { return _safeLanding->loadingProgressPercentage(); } signals: void packetVersionMismatch(); @@ -40,4 +41,4 @@ private slots: private: std::unique_ptr _safeLanding; }; -#endif // hifi_OctreePacketProcessor_h +#endif // hifi_OctreePacketProcessor_h diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index cfb0a2c753..9785e0ba95 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; @@ -53,6 +54,7 @@ void SafeLanding::startEntitySequence(QSharedPointer entityT void SafeLanding::stopEntitySequence() { Locker lock(_lock); _trackingEntities = false; + _maxTrackedEntityCount = 0; _initialStart = INVALID_SEQUENCE; _initialEnd = INVALID_SEQUENCE; _trackedEntities.clear(); @@ -64,20 +66,13 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { Locker lock(_lock); EntityItemPointer entity = _entityTree->findEntityByID(entityID); - if (entity && !entity->getCollisionless()) { - const auto& entityType = entity->getType(); - if (entityType == EntityTypes::Model) { - ModelEntityItem * modelEntity = std::dynamic_pointer_cast(entity).get(); - static const std::set downloadedCollisionTypes - { SHAPE_TYPE_COMPOUND, SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_SIMPLE_HULL }; - bool hasAABox; - entity->getAABox(hasAABox); - if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { - // Only track entities with downloaded collision bodies. - _trackedEntities.emplace(entityID, entity); - } - } + _trackedEntities.emplace(entityID, entity); + int trackedEntityCount = (int)_trackedEntities.size(); + + if (trackedEntityCount > _maxTrackedEntityCount) { + _maxTrackedEntityCount = trackedEntityCount; } + qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); } } @@ -102,7 +97,7 @@ void SafeLanding::noteReceivedsequenceNumber(int sequenceNumber) { } bool SafeLanding::isLoadSequenceComplete() { - if (isEntityPhysicsComplete() && isSequenceNumbersComplete()) { + if (isEntityLoadingComplete() && isSequenceNumbersComplete()) { Locker lock(_lock); _trackedEntities.clear(); _initialStart = INVALID_SEQUENCE; @@ -114,6 +109,15 @@ bool SafeLanding::isLoadSequenceComplete() { return !_trackingEntities; } +float SafeLanding::loadingProgressPercentage() { + Locker lock(_lock); + if (_maxTrackedEntityCount > 0) { + return ((_maxTrackedEntityCount - _trackedEntities.size()) / (float)_maxTrackedEntityCount); + } + + return 0.0f; +} + bool SafeLanding::isSequenceNumbersComplete() { if (_initialStart != INVALID_SEQUENCE) { Locker lock(_lock); @@ -132,17 +136,42 @@ bool SafeLanding::isSequenceNumbersComplete() { return false; } -bool SafeLanding::isEntityPhysicsComplete() { - Locker lock(_lock); - for (auto entityMapIter = _trackedEntities.begin(); entityMapIter != _trackedEntities.end(); ++entityMapIter) { - auto entity = entityMapIter->second; - if (!entity->shouldBePhysical() || entity->isReadyToComputeShape()) { - entityMapIter = _trackedEntities.erase(entityMapIter); - if (entityMapIter == _trackedEntities.end()) { - break; +bool isEntityPhysicsReady(const EntityItemPointer& entity) { + if (entity && !entity->getCollisionless()) { + const auto& entityType = entity->getType(); + if (entityType == EntityTypes::Model) { + ModelEntityItem * modelEntity = std::dynamic_pointer_cast(entity).get(); + static const std::set downloadedCollisionTypes + { SHAPE_TYPE_COMPOUND, SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_SIMPLE_HULL }; + bool hasAABox; + entity->getAABox(hasAABox); + if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { + return entity->isReadyToComputeShape(); } } } + + return true; +} + +bool SafeLanding::isEntityLoadingComplete() { + Locker lock(_lock); + auto entityTree = qApp->getEntities(); + auto entityMapIter = _trackedEntities.begin(); + + while (entityMapIter != _trackedEntities.end()) { + auto entity = entityMapIter->second; + bool isVisuallyReady = (entity->isVisuallyReady() || !entityTree->renderableForEntityId(entityMapIter->first)); + if (isEntityPhysicsReady(entity) && isVisuallyReady) { + entityMapIter = _trackedEntities.erase(entityMapIter); + } else { + if (!isVisuallyReady) { + entity->requestRenderUpdate(); + } + + entityMapIter++; + } + } return _trackedEntities.empty(); } diff --git a/interface/src/octree/SafeLanding.h b/interface/src/octree/SafeLanding.h index 9177930d81..317e4587c7 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,7 @@ public: void setCompletionSequenceNumbers(int first, int last); // 'last' exclusive. void noteReceivedsequenceNumber(int sequenceNumber); bool isLoadSequenceComplete(); + float loadingProgressPercentage(); private slots: void addTrackedEntity(const EntityItemID& entityID); @@ -37,7 +39,7 @@ private slots: private: bool isSequenceNumbersComplete(); void debugDumpSequenceIDs() const; - bool isEntityPhysicsComplete(); + bool isEntityLoadingComplete(); std::mutex _lock; using Locker = std::lock_guard; @@ -49,6 +51,7 @@ private: static constexpr int INVALID_SEQUENCE = -1; int _initialStart { INVALID_SEQUENCE }; int _initialEnd { INVALID_SEQUENCE }; + int _maxTrackedEntityCount { 0 }; 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..e3ae65aee1 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -409,6 +409,10 @@ glm::vec2 WindowScriptingInterface::getDeviceSize() const { return qApp->getDeviceSize(); } +int WindowScriptingInterface::getLastDomainConnectionError() const { + return DependencyManager::get()->getDomainHandler().getLastDomainConnectionError(); +} + int WindowScriptingInterface::getX() { return qApp->getWindow()->geometry().x(); } @@ -584,3 +588,8 @@ void WindowScriptingInterface::onMessageBoxSelected(int button) { _messageBoxes.remove(id); } } + + +float WindowScriptingInterface::domainLoadingProgress() { + return qApp->getOctreePacketProcessor().domainLoadingProgress(); +} diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 77895e0e76..3827406729 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -491,6 +491,13 @@ public slots: */ glm::vec2 getDeviceSize() const; + /**jsdoc + * Gets the last domain connection error when a connection is refused. + * @function Window.getLastDomainConnectionError + * @returns {Window.ConnectionRefusedReason} Integer number that enumerates the last domain connection refused. + */ + int getLastDomainConnectionError() const; + /**jsdoc * Open a non-modal message box that can have a variety of button combinations. See also, * {@link Window.updateMessageBox|updateMessageBox} and {@link Window.closeMessageBox|closeMessageBox}. @@ -561,6 +568,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 d51b90f15d..8faa51bc58 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..3a33eccc8a 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -305,6 +305,16 @@ void AudioClient::audioMixerKilled() { emit disconnected(); } +void AudioClient::setAudioPaused(bool pause) { + if (_audioPaused != pause) { + _audioPaused = pause; + + if (!_audioPaused) { + negotiateAudioFormat(); + } + } +} + QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { QAudioDeviceInfo result; foreach(QAudioDeviceInfo audioDevice, getAvailableDevices(mode)) { @@ -651,7 +661,6 @@ void AudioClient::stop() { } void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - char bitset; message->readPrimitive(&bitset); @@ -664,11 +673,10 @@ void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - if (message->getType() == PacketType::SilentAudioFrame) { _silentInbound.increment(); } else { @@ -1026,80 +1034,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 (!_audioPaused) { + 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..4640d7c045 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -162,6 +162,7 @@ public: bool startRecording(const QString& filename); void stopRecording(); + void setAudioPaused(bool pause); #ifdef Q_OS_WIN @@ -416,6 +417,7 @@ private: QVector _activeLocalAudioInjectors; bool _isPlayingBackRecording { false }; + bool _audioPaused { false }; 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/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 00e552af89..9e5cbcaa7b 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -31,6 +31,7 @@ #include "udt/PacketHeaders.h" const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json"; +const QString REDIRECT_HIFI_ADDRESS = "file:///~/serverless/redirect.json"; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; @@ -111,6 +112,9 @@ QUrl AddressManager::currentFacingPublicAddress() const { return shareableAddress; } +QUrl AddressManager::lastAddress() const { + return _lastVisitedURL; +} void AddressManager::loadSettings(const QString& lookupString) { #if defined(USE_GLES) && defined(Q_OS_WIN) @@ -247,9 +251,12 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // in case we're failing to connect to where we thought this user was // store their username as previous lookup so we can refresh their location via API - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; } else { // we're assuming this is either a network address or global place name // check if it is a network address first @@ -259,8 +266,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_NETWORK_ADDRESS, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // a network address lookup clears the previous lookup since we don't expect to re-attempt it - _previousLookup.clear(); + _previousAPILookup.clear(); // If the host changed then we have already saved to history if (hostChanged) { @@ -278,8 +288,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } else if (handleDomainID(lookupUrl.host())){ UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_DOMAIN_ID, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; // no place name - this is probably a domain ID // try to look up the domain ID on the metaverse API @@ -287,8 +300,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } else { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_PLACE, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // store this place name as the previous lookup in case we fail to connect and want to refresh API info - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; // wasn't an address - lookup the place name // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after @@ -302,7 +318,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { qCDebug(networking) << "Going to relative path" << lookupUrl.path(); // a path lookup clears the previous lookup since we don't expect to re-attempt it - _previousLookup.clear(); + _previousAPILookup.clear(); // if this is a relative path then handle it as a relative viewpoint handlePath(lookupUrl.path(), trigger, true); @@ -314,7 +330,10 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // be loaded over http(s) // lookupUrl.scheme() == URL_SCHEME_HTTP || // lookupUrl.scheme() == URL_SCHEME_HTTPS || - _previousLookup.clear(); + // TODO once a file can return a connection refusal if there were to be some kind of load error, we'd + // need to store the previous domain tried in _lastVisitedURL. For now , do not store it. + + _previousAPILookup.clear(); _shareablePlaceName.clear(); setDomainInfo(lookupUrl, trigger); emit lookupResultsFinished(); @@ -381,7 +400,7 @@ void AddressManager::handleAPIResponse(QNetworkReply* requestReply) { QJsonObject dataObject = responseObject["data"].toObject(); // Lookup succeeded, don't keep re-trying it (especially on server restarts) - _previousLookup.clear(); + _previousAPILookup.clear(); if (!dataObject.isEmpty()) { goToAddressFromObject(dataObject.toVariantMap(), requestReply); @@ -547,7 +566,7 @@ void AddressManager::handleAPIError(QNetworkReply* errorReply) { if (errorReply->error() == QNetworkReply::ContentNotFoundError) { // if this is a lookup that has no result, don't keep re-trying it - _previousLookup.clear(); + _previousAPILookup.clear(); emit lookupResultIsNotFound(); } @@ -709,7 +728,6 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should // We use _newHostLookupPath to determine if the client has already stored its last address // before moving to a new host thanks to the information in the same lookup URL. - if (definitelyPathOnly || (!pathString.isEmpty() && pathString != _newHostLookupPath) || trigger == Back || trigger == Forward) { addCurrentAddressToHistory(trigger); @@ -843,8 +861,8 @@ void AddressManager::goToUser(const QString& username, bool shouldMatchOrientati void AddressManager::refreshPreviousLookup() { // if we have a non-empty previous lookup, fire it again now (but don't re-store it in the history) - if (!_previousLookup.isEmpty()) { - handleUrl(_previousLookup, LookupTrigger::AttemptedRefresh); + if (!_previousAPILookup.isEmpty()) { + handleUrl(_previousAPILookup, LookupTrigger::AttemptedRefresh); } else { handleUrl(currentAddress(), LookupTrigger::AttemptedRefresh); } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 37b85a9acd..c7cdf8f4ea 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -23,6 +23,7 @@ #include "AccountManager.h" extern const QString DEFAULT_HIFI_ADDRESS; +extern const QString REDIRECT_HIFI_ADDRESS; const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost"; const QString INDEX_PATH = "/"; @@ -55,7 +56,6 @@ const QString GET_PLACE = "/api/v1/places/%1"; * Read-only. * @property {boolean} isConnected - true if you're connected to the domain in your current href * metaverse address, otherwise false. - * Read-only. * @property {string} pathname - The location and orientation in your current href metaverse address * (e.g., "/15,-10,26/0,0,0,1"). * Read-only. @@ -140,7 +140,8 @@ public: * * @typedef {number} location.LookupTrigger */ - enum LookupTrigger { + enum LookupTrigger + { UserInput, Back, Forward, @@ -164,6 +165,8 @@ public: QString currentPath(bool withOrientation = true) const; QString currentFacingPath() const; + QUrl lastAddress() const; + const QUuid& getRootPlaceID() const { return _rootPlaceID; } QString getPlaceName() const; QString getDomainID() const; @@ -191,7 +194,7 @@ public slots: * Helps ensure that user's location history is correctly maintained. */ void handleLookupString(const QString& lookupString, bool fromSuggestions = false); - + /**jsdoc * Go to a position and orientation resulting from a lookup for a named path in the domain (set in the domain server's * settings). @@ -204,8 +207,9 @@ public slots: // functions and signals that should be exposed are moved to a scripting interface class. // // we currently expect this to be called from NodeList once handleLookupString has been called with a path - bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) - { return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); } + bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) { + return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); + } /**jsdoc * Go back to the previous location in your navigation history, if there is one. @@ -226,8 +230,10 @@ public slots: * @param {location.LookupTrigger} trigger=StartupFromSettings - The reason for the function call. Helps ensure that user's * location history is correctly maintained. */ - void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); } - + void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { + handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); + } + /**jsdoc * Go to the default "welcome" metaverse address. * @function location.goToEntry @@ -245,6 +251,12 @@ public slots: */ void goToUser(const QString& username, bool shouldMatchOrientation = true); + /**jsdoc + * Go to the last address tried. This will be the last URL tried from location.handleLookupString + * @function location.goToLastAddress + */ + void goToLastAddress() { handleUrl(_lastVisitedURL, LookupTrigger::AttemptedRefresh); } + /**jsdoc * Refresh the current address, e.g., after connecting to a domain in order to position the user to the desired location. * @function location.refreshPreviousLookup @@ -352,7 +364,8 @@ signals: * location.locationChangeRequired.connect(onLocationChangeRequired); */ void locationChangeRequired(const glm::vec3& newPosition, - bool hasOrientationChange, const glm::quat& newOrientation, + bool hasOrientationChange, + const glm::quat& newOrientation, bool shouldFaceLocation); /**jsdoc @@ -423,7 +436,7 @@ private slots: void handleShareableNameAPIResponse(QNetworkReply* requestReply); private: - void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply* reply); + void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply* reply); // Set host and port, and return `true` if it was changed. bool setHost(const QString& host, LookupTrigger trigger, quint16 port = 0); @@ -435,8 +448,11 @@ private: bool handleNetworkAddress(const QString& lookupString, LookupTrigger trigger, bool& hostChanged); void handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly = false); - bool handleViewpoint(const QString& viewpointString, bool shouldFace, LookupTrigger trigger, - bool definitelyPathOnly = false, const QString& pathString = QString()); + bool handleViewpoint(const QString& viewpointString, + bool shouldFace, + LookupTrigger trigger, + bool definitelyPathOnly = false, + const QString& pathString = QString()); bool handleUsername(const QString& lookupString); bool handleDomainID(const QString& host); @@ -446,6 +462,7 @@ private: void addCurrentAddressToHistory(LookupTrigger trigger); QUrl _domainURL; + QUrl _lastVisitedURL; QUuid _rootPlaceID; PositionGetter _positionGetter; @@ -459,7 +476,7 @@ private: QString _newHostLookupPath; - QUrl _previousLookup; + QUrl _previousAPILookup; }; -#endif // hifi_AddressManager_h +#endif // hifi_AddressManager_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 39c8b5b1a1..59e3de922f 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -99,6 +99,7 @@ void DomainHandler::softReset() { clearSettings(); + _isInErrorState = false; _connectionDenialsSinceKeypairRegen = 0; _checkInPacketsSinceLastReply = 0; @@ -128,6 +129,11 @@ void DomainHandler::hardReset() { _pendingPath.clear(); } +void DomainHandler::setErrorDomainURL(const QUrl& url) { + _errorDomainURL = url; + return; +} + void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) { if (_sockAddr != sockAddr) { // we should reset on a sockAddr change @@ -171,7 +177,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { domainPort = DEFAULT_DOMAIN_SERVER_PORT; } - if (_domainURL != domainURL || _sockAddr.getPort() != domainPort) { + // if it's in the error state, reset and try again. + if ((_domainURL != domainURL || _sockAddr.getPort() != domainPort) || _isInErrorState) { // re-set the domain info so that auth information is reloaded hardReset(); @@ -206,7 +213,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) { - if (_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) { + // if it's in the error state, reset and try again. + if ((_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) || _isInErrorState) { // re-set the domain info to connect to new domain hardReset(); @@ -316,6 +324,23 @@ void DomainHandler::connectedToServerless(std::map namedPaths) setIsConnected(true); } +void DomainHandler::loadedErrorDomain(std::map namedPaths) { + auto lookup = namedPaths.find("/"); + QString viewpoint; + if (lookup != namedPaths.end()) { + viewpoint = lookup->second; + } else { + viewpoint = DOMAIN_SPAWNING_POINT; + } + DependencyManager::get()->goToViewpointForPath(viewpoint, QString()); +} + +void DomainHandler::setRedirectErrorState(QUrl errorUrl, int reasonCode) { + _errorDomainURL = errorUrl; + _lastDomainConnectionError = reasonCode; + emit redirectToErrorDomainURL(_errorDomainURL); +} + void DomainHandler::requestDomainSettings() { qCDebug(networking) << "Requesting settings from domain server"; @@ -451,7 +476,18 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index a428110db6..ab20936f43 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -50,6 +50,11 @@ public: QString getHostname() const { return _domainURL.host(); } + QUrl getErrorDomainURL(){ return _errorDomainURL; } + void setErrorDomainURL(const QUrl& url); + + int getLastDomainConnectionError() { return _lastDomainConnectionError; } + const QHostAddress& getIP() const { return _sockAddr.getAddress(); } void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); } @@ -81,6 +86,10 @@ public: void connectedToServerless(std::map namedPaths); + void loadedErrorDomain(std::map namedPaths); + // sets domain handler in error state. + void setRedirectErrorState(QUrl errorUrl, int reasonCode); + QString getViewPointFromNamedPath(QString namedPath); bool hasSettings() const { return !_settingsObject.isEmpty(); } @@ -135,6 +144,11 @@ public: * 4 * The domain already has its maximum number of users. * + * + * TimedOut + * 5 + * Connecting to the domain timed out. + * * * * @typedef {number} Window.ConnectionRefusedReason @@ -144,7 +158,8 @@ public: ProtocolMismatch, LoginError, NotAuthorized, - TooManyUsers + TooManyUsers, + TimedOut }; public slots: @@ -164,6 +179,8 @@ private slots: signals: void domainURLChanged(QUrl domainURL); + void domainConnectionErrorChanged(int reasonCode); + // NOTE: the emission of completedSocketDiscovery does not mean a connection to DS is established // It means that, either from DNS lookup or ICE, we think we have a socket we can talk to DS on void completedSocketDiscovery(); @@ -179,6 +196,7 @@ signals: void settingsReceiveFail(); void domainConnectionRefused(QString reasonMessage, int reason, const QString& extraInfo); + void redirectToErrorDomainURL(QUrl errorDomainURL); void limitOfSilentDomainCheckInsReached(); @@ -190,6 +208,7 @@ private: QUuid _uuid; Node::LocalID _localID; QUrl _domainURL; + QUrl _errorDomainURL; HifiSockAddr _sockAddr; QUuid _assignmentUUID; QUuid _connectionToken; @@ -198,6 +217,7 @@ private: HifiSockAddr _iceServerSockAddr; NetworkPeer _icePeer; bool _isConnected { false }; + bool _isInErrorState { false }; QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; @@ -210,6 +230,9 @@ private: QTimer _apiRefreshTimer; std::map _namedPaths; + + // domain connection error upon connection refusal. + int _lastDomainConnectionError{ -1 }; }; const QString DOMAIN_SPAWNING_POINT { "/0, -10, 0" }; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index b275660c0f..31510831c8 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -35,6 +35,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", + "system/interstitialPage.js" //"system/chat.js" ]; diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js new file mode 100644 index 0000000000..dcce721cd9 --- /dev/null +++ b/scripts/system/interstitialPage.js @@ -0,0 +1,476 @@ +// +// 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 MIN_LOADING_PROGRESS = 3.6; + var TOTAL_LOADING_PROGRESS = 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: 24, y: 24, z: 24 }; + + 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.001 }, + text: domainName, + textAlpha: 1, + backgroundAlpha: 1, + 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: 1, + lineHeight: 0.13, + visible: isVisible, + backgroundAlpha: 0, + 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: 1, + 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) { + 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; + 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 = (HifiAbout.buildVersion === "dev") ? "hifi://TheSpot-dev": "hifi://TheSpot"; + function clickedOnOverlay(overlayID, event) { + 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); + + + Menu.setIsOptionChecked("Show Overlays", physicsEnabled); + if (!HMD.active) { + toolbar.writeProperty("visible", physicsEnabled); + } + + 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 = MIN_LOADING_PROGRESS * domainLoadingProgressPercentage; + if (progress >= target) { + target = progress; + } + + if ((physicsEnabled && (currentProgress < TOTAL_LOADING_PROGRESS))) { + target = TOTAL_LOADING_PROGRESS; + } + + currentProgress = lerp(currentProgress, target, 0.2); + var properties = { + localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / TOTAL_LOADING_PROGRESS))), y: -0.935, z: 0.0 }, + dimensions: { + x: currentProgress, + y: 2.8 + } + }; + + Overlays.editOverlay(loadingBarProgress, properties); + if ((physicsEnabled && (currentProgress >= (TOTAL_LOADING_PROGRESS - EPSILON)))) { + updateOverlays((physicsEnabled || connectionToDomainFailed)); + endAudio(); + currentDomain = "no domain"; + timer = null; + return; + } + + timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + } + var whiteColor = {red: 255, green: 255, blue: 255}; + var greyColor = {red: 125, green: 125, blue: 125}; + Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); + Overlays.hoverEnterOverlay.connect(function(overlayID, event) { + if (overlayID === loadingToTheSpotID) { + Overlays.editOverlay(loadingToTheSpotID, { color: greyColor}); + } + }); + + Overlays.hoverLeaveOverlay.connect(function(overlayID, event) { + if (overlayID === loadingToTheSpotID) { + Overlays.editOverlay(loadingToTheSpotID, { color: whiteColor}); + } + }); + + location.hostChanged.connect(domainChanged); + location.lookupResultsFinished.connect(function() { + Script.setTimeout(function() { + connectionToDomainFailed = !location.isConnected; + }, 1200); + }); + + MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); + MyAvatar.sessionUUIDChanged.connect(function() { + var avatarSessionUUID = MyAvatar.sessionUUID; + Overlays.editOverlay(loadingSphereID, { parentID: avatarSessionUUID }); + }); + + 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; + Menu.setIsOptionChecked("Show Overlays", true); + if (!HMD.active) { + toolbar.writeProperty("visible", true); + } + } + + Script.scriptEnding.connect(cleanup); +}());