History

Merge branch 'master' of github.com:highfidelity/hifi into audio/parity

Zach Pomerantz committed 9 years ago

Commit a8f69bb2 parents e7da007 ae81738385 files · +9641 −4012Browse files

Unified · Split

385 files
  1. BUILD.md +15 −10
  2. BUILD_ANDROID.md +9 −9
  3. BUILD_LINUX.md +2 −1
  4. BUILD_OSX.md +7 −4
  5. BUILD_WIN.md +33 −29
  6. INSTALL.md +4 −4
  7. assignment-client/src/Agent.cpp +2 −5
  8. assignment-client/src/AssignmentClient.cpp +1 −17
  9. assignment-client/src/audio/AudioMixerSlavePool.h +2 −2
  10. assignment-client/src/avatars/AvatarMixerSlavePool.h +1 −2
  11. assignment-client/src/scripts/EntityScriptServer.cpp +1 −5
  12. cmake/externals/nvtt/CMakeLists.txt +4 −4
  13. domain-server/resources/web/content/index.shtml +1 −1
  14. gvr-interface/src/RenderingClient.cpp +2 −10
  15. interface/resources/avatar/avatar-animation.json +3 −3
  16. interface/resources/config/render.json +9 −7
  17. interface/resources/controllers/oculus_touch.json +3 −1
  18. interface/resources/controllers/vive.json +8 −8
  19. interface/resources/fonts/hifi-glyphs.ttf +0 −0
  20. interface/resources/html/createGlobalEventBridge.js +1 −1
  21. interface/resources/meshes/Jointy3/Jointy3.fbx +0 −0
  22. interface/resources/meshes/defaultAvatar_full.fst +73 −69
  23. interface/resources/meshes/mannequin/Eyes.ktx +0 −0
  24. interface/resources/meshes/mannequin/lambert1_Base_Color.ktx +0 −0
  25. interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx +0 −0
  26. interface/resources/meshes/mannequin/lambert1_Roughness.ktx +0 −0
  27. interface/resources/meshes/mannequin/mannequin.baked.fbx +0 −0
  28. interface/resources/qml/AssetServer.qml +1 −1
  29. interface/resources/qml/Browser.qml +3 −18
  30. interface/resources/qml/QmlWebWindow.qml +2 −8
  31. interface/resources/qml/QmlWindow.qml +0 −9
  32. interface/resources/qml/TabletBrowser.qml +4 −14
  33. interface/resources/qml/ToolWindow.qml +3 −10
  34. interface/resources/qml/controls-uit/BaseWebView.qml +0 −3
  35. interface/resources/qml/controls/TabletWebScreen.qml +4 −14
  36. interface/resources/qml/controls/TabletWebView.qml +3 −14
  37. interface/resources/qml/controls/WebEntityView.qml +1 −5
  38. interface/resources/qml/controls/WebView.qml +3 −14
  39. interface/resources/qml/dialogs/TabletLoginDialog.qml +0 −1
  40. interface/resources/qml/dialogs/preferences/AvatarBrowser.qml +4 −12
  41. interface/resources/qml/dialogs/preferences/AvatarPreference.qml +1 −3
  42. interface/resources/qml/hifi/Desktop.qml +0 −6
  43. interface/resources/qml/hifi/Pal.qml +7 −4
  44. interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +1 −2
  45. interface/resources/qml/hifi/dialogs/TabletDCDialog.qml +0 −1
  46. interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml +0 −1
  47. interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml +0 −1
  48. interface/resources/qml/hifi/dialogs/TabletLODTools.qml +0 −1
  49. interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml +0 −1
  50. interface/resources/qml/hifi/tablet/Edit.qml +0 −2
  51. interface/resources/qml/hifi/tablet/EditTabView.qml +1 −6
  52. interface/resources/qml/hifi/tablet/InputRecorder.qml +0 −1
  53. interface/resources/qml/hifi/tablet/NewModelDialog.qml +1 −2
  54. interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +0 −2
  55. interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml +0 −1
  56. interface/resources/qml/hifi/tablet/TabletAudioBuffers.qml +1 −0
  57. interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml +0 −1
  58. interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml +0 −1
  59. interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml +0 −1
  60. interface/resources/qml/hifi/tablet/TabletLodPreferences.qml +0 −1
  61. interface/resources/qml/hifi/tablet/TabletMenu.qml +0 −2
  62. interface/resources/qml/hifi/tablet/TabletMenuStack.qml +0 −1
  63. interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml +0 −1
  64. interface/resources/qml/hifi/tablet/TabletRoot.qml +11 −14
  65. interface/resources/qml/hifi/tablet/WindowRoot.qml +11 −11
  66. interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +1 −1
  67. interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml +5 −10
  68. interface/resources/snapshot/img/no-image.jpg +0 −0
  69. interface/src/Application.cpp +283 −241
  70. interface/src/Application.h +7 −5
  71. interface/src/AvatarBookmarks.cpp +22 −2
  72. interface/src/AvatarBookmarks.h +1 −0
  73. interface/src/CrashHandler.cpp +20 −8
  74. interface/src/CrashHandler.h +1 −1
  75. interface/src/DiscoverabilityManager.cpp +1 −1
  76. interface/src/Menu.cpp +6 −0
  77. interface/src/Menu.h +1 −0
  78. interface/src/avatar/MyAvatar.cpp +276 −57
  79. interface/src/avatar/MyAvatar.h +24 −10
  80. interface/src/avatar/MyHead.cpp +9 −8
  81. interface/src/avatar/MyHead.h +1 −1
  82. interface/src/avatar/MySkeletonModel.cpp +8 −16
  83. interface/src/main.cpp +2 −6
  84. interface/src/networking/CloseEventSender.cpp +95 −0
  85. interface/src/networking/CloseEventSender.h +42 −0
  86. interface/src/scripting/HMDScriptingInterface.cpp +4 −0
  87. interface/src/scripting/HMDScriptingInterface.h +1 −0
  88. interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp +5 −4
  89. interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h +0 −1
  90. interface/src/ui/ModelsBrowser.cpp +2 −6
  91. interface/src/ui/overlays/Text3DOverlay.cpp +13 −3
  92. interface/src/ui/overlays/Text3DOverlay.h +7 −6
  93. interface/src/ui/overlays/Web3DOverlay.cpp +35 −33
  94. libraries/animation/src/AnimInverseKinematics.cpp +294 −23
  95. libraries/animation/src/AnimInverseKinematics.h +29 −3
  96. libraries/animation/src/IKTarget.cpp +3 −0
  97. libraries/animation/src/IKTarget.h +1 −0
  98. libraries/animation/src/Rig.cpp +66 −32
  99. libraries/animation/src/Rig.h +7 −4
  100. libraries/animation/src/RotationAccumulator.cpp +2 −2
  101. libraries/animation/src/TranslationAccumulator.cpp +34 −0
  102. libraries/animation/src/TranslationAccumulator.h +42 −0
  103. libraries/audio-client/src/AudioClient.cpp +37 −78
  104. libraries/audio-client/src/AudioClient.h +2 −2
  105. libraries/audio/src/Sound.cpp +45 −16
  106. libraries/audio/src/Sound.h +30 −3
  107. libraries/avatars-renderer/src/avatars-renderer/Head.cpp +0 −12
  108. libraries/avatars-renderer/src/avatars-renderer/Head.h +0 −3
  109. libraries/avatars/src/AvatarData.cpp +4 −1
  110. libraries/avatars/src/HeadData.h +0 −3
  111. libraries/controllers/src/controllers/Actions.h +2 −2
  112. libraries/controllers/src/controllers/InputRecorder.cpp +77 −46
  113. libraries/controllers/src/controllers/InputRecorder.h +11 −8
  114. libraries/controllers/src/controllers/Pose.h +1 −0
  115. libraries/controllers/src/controllers/UserInputMapper.cpp +10 −0
  116. libraries/controllers/src/controllers/UserInputMapper.h +1 −0
  117. libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp +7 −8
  118. libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h +11 −0
  119. libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h +3 −3
  120. libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +12 −0
  121. libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +6 −0
  122. libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +6 −0
  123. libraries/entities-renderer/src/EntityTreeRenderer.cpp +1 −221
  124. libraries/entities-renderer/src/EntityTreeRenderer.h +0 −3
  125. libraries/entities-renderer/src/RenderableModelEntityItem.cpp +1 −1
  126. libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +3 −0
  127. libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +2 −0
  128. libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +5 −0
  129. libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +2 −0
  130. libraries/entities-renderer/src/RenderableWebEntityItem.cpp +4 −4
  131. libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +408 −38
  132. libraries/entities-renderer/src/RenderableZoneEntityItem.h +14 −3
  133. libraries/entities/src/EntityItem.cpp +7 −17
  134. libraries/entities/src/EntityItem.h +2 −2
  135. libraries/entities/src/EntityScriptingInterface.cpp +6 −4
  136. libraries/entities/src/SimulationFlags.h +1 −3
  137. libraries/entities/src/SimulationOwner.cpp +11 −11
  138. libraries/entities/src/SimulationOwner.h +3 −2
  139. libraries/entities/src/ZoneEntityItem.cpp +39 −20
  140. libraries/entities/src/ZoneEntityItem.h +15 −1
  141. libraries/fbx/src/FBXReader.cpp +4 −10
  142. libraries/gl/CMakeLists.txt +1 −1
  143. libraries/gl/src/gl/OffscreenQmlSurface.cpp +152 −75
  144. libraries/gl/src/gl/OffscreenQmlSurface.h +5 −5
  145. libraries/gl/src/gl/OffscreenQmlSurfaceCache.cpp +0 −1
  146. libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp +13 −2
  147. libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +4 −0
  148. libraries/gpu/src/gpu/DrawTransformVertexPosition.slv +28 −0
  149. libraries/gpu/src/gpu/DrawVertexPosition.slv +6 −13
  150. libraries/gpu/src/gpu/DrawWhite.slf +6 −4
  151. libraries/gpu/src/gpu/StandardShaderLib.cpp +38 −2
  152. libraries/gpu/src/gpu/StandardShaderLib.h +15 −0
  153. libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +0 −2
  154. libraries/input-plugins/src/input-plugins/TouchscreenDevice.h +0 −2
  155. libraries/ktx/src/khronos/KHR.h +2 −0
  156. libraries/ktx/src/ktx/Writer.cpp +1 −1
  157. libraries/model-networking/src/model-networking/KTXCache.cpp +14 −0
  158. libraries/model-networking/src/model-networking/KTXCache.h +6 −0
  159. libraries/model/src/model/Geometry.cpp +38 −0
  160. libraries/model/src/model/Geometry.h +6 −0
  161. libraries/model/src/model/Light.cpp +1 −1
  162. libraries/model/src/model/Light.h +3 −3
  163. libraries/model/src/model/Skybox.cpp +1 −1
  164. libraries/model/src/model/Skybox.h +4 −0
  165. libraries/networking/CMakeLists.txt +1 −1
  166. libraries/networking/src/AccountManager.cpp +7 −1
  167. libraries/networking/src/AccountManager.h +2 −1
  168. libraries/networking/src/AssetRequest.cpp +7 −2
  169. libraries/networking/src/AssetRequest.h +3 −0
  170. libraries/networking/src/AssetResourceRequest.cpp +33 −3
  171. libraries/networking/src/FileCache.cpp +146 −64
  172. libraries/networking/src/FileCache.h +42 −24
  173. libraries/networking/src/FileResourceRequest.cpp +11 −0
  174. libraries/networking/src/FileTypeProfile.cpp +0 −0
  175. libraries/networking/src/FileTypeProfile.h +0 −0
  176. libraries/networking/src/FileTypeRequestInterceptor.cpp +0 −0
  177. libraries/networking/src/FileTypeRequestInterceptor.h +0 −0
  178. libraries/networking/src/HFTabletWebEngineProfile.cpp +1 −0
  179. libraries/networking/src/HFTabletWebEngineProfile.h +0 −0
  180. libraries/networking/src/HFTabletWebEngineRequestInterceptor.cpp +1 −1
  181. libraries/networking/src/HFTabletWebEngineRequestInterceptor.h +0 −0
  182. libraries/networking/src/HFWebEngineProfile.cpp +1 −0
  183. libraries/networking/src/HFWebEngineProfile.h +0 −0
  184. libraries/networking/src/HFWebEngineRequestInterceptor.cpp +1 −1
  185. libraries/networking/src/HFWebEngineRequestInterceptor.h +0 −0
  186. libraries/networking/src/HTTPResourceRequest.cpp +16 −0
  187. libraries/networking/src/LimitedNodeList.cpp +2 −4
  188. libraries/networking/src/LimitedNodeList.h +4 −3
  189. libraries/networking/src/MessagesClient.cpp +6 −6
  190. libraries/networking/src/MessagesClient.h +1 −1
  191. libraries/networking/src/Node.h +1 −1
  192. libraries/networking/src/NodeList.cpp +6 −0
  193. libraries/networking/src/NodeList.h +2 −1
  194. libraries/networking/src/RequestFilters.cpp +1 −1
  195. libraries/networking/src/RequestFilters.h +0 −0
  196. libraries/networking/src/ResourceRequest.h +15 −0
  197. libraries/networking/src/SandboxUtils.cpp +3 −4
  198. libraries/networking/src/SandboxUtils.h +1 −1
  199. libraries/networking/src/UserActivityLogger.cpp +9 −1
  200. libraries/networking/src/UserActivityLogger.h +6 −1
  201. libraries/networking/src/UserActivityLoggerScriptingInterface.cpp +9 −1
  202. libraries/networking/src/UserActivityLoggerScriptingInterface.h +2 −0
  203. libraries/networking/src/udt/PacketHeaders.cpp +1 −1
  204. libraries/networking/src/udt/PacketHeaders.h +2 −1
  205. libraries/networking/src/udt/SendQueue.cpp +3 −0
  206. libraries/physics/src/CharacterController.cpp +13 −3
  207. libraries/physics/src/CharacterController.h +2 −0
  208. libraries/physics/src/EntityMotionState.cpp +22 −15
  209. libraries/physics/src/EntityMotionState.h +1 −0
  210. libraries/physics/src/ObjectMotionState.cpp +2 −0
  211. libraries/physics/src/ObjectMotionState.h +2 −1
  212. libraries/physics/src/PhysicalEntitySimulation.cpp +1 −1
  213. libraries/physics/src/PhysicsEngine.cpp +25 −20
  214. libraries/physics/src/PhysicsEngine.h +3 −2
  215. libraries/physics/src/ThreadSafeDynamicsWorld.h +2 −0
  216. libraries/plugins/src/plugins/DisplayPlugin.h +1 −0
  217. libraries/plugins/src/plugins/InputPlugin.h +2 −1
  218. libraries/plugins/src/plugins/Plugin.h +13 −0
  219. libraries/plugins/src/plugins/PluginUtils.cpp +9 −0
  220. libraries/plugins/src/plugins/PluginUtils.h +1 −0
  221. libraries/procedural/src/procedural/ProceduralSkybox.cpp +1 −1
  222. libraries/render-utils/CMakeLists.txt +1 −1
  223. libraries/render-utils/src/AnimDebugDraw.cpp +1 −1
  224. libraries/render-utils/src/AntialiasingEffect.cpp +4 −4
  225. libraries/render-utils/src/BackgroundStage.cpp +140 −0
  226. libraries/render-utils/src/BackgroundStage.h +80 −0
  227. libraries/render-utils/src/DeferredLightingEffect.cpp +51 −126
  228. libraries/render-utils/src/DeferredLightingEffect.h +17 −26
  229. libraries/render-utils/src/GeometryCache.cpp +94 −30
  230. libraries/render-utils/src/GeometryCache.h +2 −2
  231. libraries/render-utils/src/HitEffect.cpp +0 −94
  232. libraries/render-utils/src/HitEffect.h +0 −38
  233. libraries/render-utils/src/LightClusters.cpp +2 −0
  234. libraries/render-utils/src/LightClusters.h +3 −3
  235. libraries/render-utils/src/LightPayload.cpp +68 −0
  236. libraries/render-utils/src/LightPayload.h +38 −0
  237. libraries/render-utils/src/LightStage.h +8 −3
  238. libraries/render-utils/src/Model.cpp +2 −2
  239. libraries/render-utils/src/Model.h +1 −1
  240. libraries/render-utils/src/RenderDeferredTask.cpp +15 −95
  241. libraries/render-utils/src/RenderDeferredTask.h +0 −29
  242. libraries/render-utils/src/RenderPipelines.cpp +2 −0
  243. libraries/render-utils/src/RenderShadowTask.cpp +3 −2
  244. libraries/render-utils/src/RenderViewTask.cpp +33 −0
  245. libraries/render-utils/src/RenderViewTask.h +31 −0
  246. libraries/render-utils/src/StencilMaskPass.cpp +126 −0
  247. libraries/render-utils/src/StencilMaskPass.h +71 −0
  248. libraries/render-utils/src/SurfaceGeometryPass.cpp +4 −3
  249. libraries/render-utils/src/ToneMappingEffect.cpp +1 −1
  250. libraries/render-utils/src/ZoneRenderer.cpp +188 −3
  251. libraries/render-utils/src/ZoneRenderer.h +40 −1
  252. libraries/render-utils/src/directional_light.slf +0 −47
  253. libraries/render-utils/src/directional_light_shadow.slf +0 −49
  254. libraries/render-utils/src/hit_effect.slf +0 −27
  255. libraries/render-utils/src/point_light.slf +0 −85
  256. libraries/render-utils/src/spot_light.slf +0 −115
  257. libraries/render-utils/src/stencil_drawMask.slf +23 −0
  258. libraries/render-utils/src/text/Font.cpp +3 −0
  259. libraries/render-utils/src/text/Font.h +1 −1
  260. libraries/render-utils/src/zone_draw.slh +37 −0
  261. libraries/render-utils/src/zone_drawAmbient.slf +54 −0
  262. libraries/render-utils/src/zone_drawKeyLight.slf +57 −0
  263. libraries/render-utils/src/zone_drawSkybox.slf +48 −0
  264. libraries/render/src/render/EngineStats.cpp +0 −2
  265. libraries/render/src/task/Config.cpp +2 −0
  266. libraries/render/src/task/Config.h +3 −2
  267. libraries/render/src/task/Task.h +1 −0
  268. libraries/script-engine/src/TabletScriptingInterface.cpp +27 −19
  269. libraries/shared/src/CubicHermiteSpline.h +113 −0
  270. libraries/shared/src/Extents.h +1 −1
  271. libraries/shared/src/GLMHelpers.cpp +24 −25
  272. libraries/shared/src/GeometryUtil.cpp +2 −2
  273. libraries/shared/src/GeometryUtil.h +3 −3
  274. libraries/shared/src/NumericalConstants.h +3 −0
  275. libraries/shared/src/RenderArgs.h +4 −4
  276. libraries/shared/src/RunningMarker.cpp +1 −34
  277. libraries/shared/src/RunningMarker.h +1 −11
  278. libraries/shared/src/TBBHelpers.h +28 −0
  279. libraries/shared/src/ThreadHelpers.cpp +39 −0
  280. libraries/shared/src/ThreadHelpers.h +10 −2
  281. libraries/shared/src/TriangleSet.cpp +6 −6
  282. libraries/shared/src/TriangleSet.h +3 −3
  283. libraries/ui/src/OffscreenUi.cpp +8 −8
  284. libraries/ui/src/QmlWindowClass.cpp +2 −2
  285. libraries/ui/src/VrMenu.cpp +1 −1
  286. plugins/hifiKinect/src/KinectPlugin.cpp +6 −2
  287. plugins/hifiKinect/src/KinectPlugin.h +1 −0
  288. plugins/hifiNeuron/src/NeuronPlugin.h +0 −2
  289. plugins/hifiSdl2/src/SDL2Manager.h +0 −1
  290. plugins/hifiSixense/src/SixenseManager.h +1 −1
  291. plugins/oculus/src/OculusBaseDisplayPlugin.cpp +10 −1
  292. plugins/oculus/src/OculusBaseDisplayPlugin.h +3 −0
  293. plugins/oculus/src/OculusControllerManager.cpp +48 −11
  294. plugins/oculus/src/OculusControllerManager.h +9 −2
  295. plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +1 −1
  296. plugins/openvr/CMakeLists.txt +3 −2
  297. plugins/openvr/src/OpenVrDisplayPlugin.cpp +30 −0
  298. plugins/openvr/src/OpenVrDisplayPlugin.h +4 −1
  299. plugins/openvr/src/OpenVrHelpers.cpp +16 −0
  300. plugins/openvr/src/OpenVrHelpers.h +1 −0
  301. plugins/openvr/src/ViveControllerManager.cpp +174 −8
  302. plugins/openvr/src/ViveControllerManager.h +11 −1
  303. scripts/defaultScripts.js +2 −1
  304. scripts/developer/tests/avatarToWorldTests.js +127 −0
  305. scripts/developer/tests/worldToAvatarTests.js +134 −0
  306. scripts/developer/utilities/render/deferredLighting.qml +16 −6
  307. scripts/developer/utilities/render/stats.qml +6 −7
  308. scripts/system/assets/sounds/snap.wav +0 −0
  309. scripts/system/assets/sounds/sound-print-photo.wav +0 −0
  310. scripts/system/bubble.js +11 −6
  311. scripts/system/chat.js +1 −1
  312. scripts/system/controllers/handControllerGrab.js +2 −1
  313. scripts/system/controllers/teleport.js +17 −37
  314. scripts/system/edit.js +173 −63
  315. scripts/system/html/ChatPage.html +0 −0
  316. scripts/system/html/SnapshotReview.html +5 −1
  317. scripts/system/html/css/SnapshotReview.css +37 −0
  318. scripts/system/html/css/edit-style.css +3 −1
  319. scripts/system/html/css/hifi-style.css +1 −1
  320. scripts/system/html/css/jsoneditor.css +1 −1
  321. scripts/system/html/entityProperties.html +3 −0
  322. scripts/system/html/img/button-snap-print.svg +25 −0
  323. scripts/system/html/js/SnapshotReview.js +101 −20
  324. scripts/system/html/js/entityProperties.js +62 −127
  325. scripts/system/html/js/eventBridgeLoader.js +1 −1
  326. scripts/system/html/js/marketplacesInject.js +2 −9
  327. scripts/system/libraries/WebTablet.js +38 −31
  328. scripts/system/libraries/entityList.js +7 −1
  329. scripts/system/libraries/gridTool.js +22 −8
  330. scripts/system/makeUserConnection.js +2 −3
  331. scripts/system/particle_explorer/dat.gui.min.js +0 −95
  332. scripts/system/particle_explorer/hifi-entity-ui.js +680 −0
  333. scripts/system/particle_explorer/particle-style.css +124 −0
  334. scripts/system/particle_explorer/particleExplorer.html +28 −81
  335. scripts/system/particle_explorer/particleExplorer.js +397 −537
  336. scripts/system/particle_explorer/particleExplorerTool.js +6 −4
  337. scripts/system/progress.js +2 −2
  338. scripts/system/snapshot.js +172 −18
  339. scripts/system/tablet-ui/tabletUI.js +2 −6
  340. server-console/src/main.js +23 −25
  341. tests/controllers/src/main.cpp +8 −0
  342. tests/ktx/CMakeLists.txt +7 −13
  343. tests/ktx/src/KtxTests.cpp +195 −0
  344. tests/ktx/src/KtxTests.h +21 −0
  345. tests/ktx/src/main.cpp +0 −223
  346. tests/networking/src/FileCacheTests.cpp +187 −0
  347. tests/networking/src/FileCacheTests.h +31 −0
  348. tests/qt59/CMakeLists.txt +15 −0
  349. tests/qt59/src/main.cpp +78 −0
  350. tests/render-perf/CMakeLists.txt +1 −1
  351. tests/shaders/src/main.cpp +0 −11
  352. tests/shared/src/CubicHermiteSplineTests.cpp +77 −0
  353. tests/shared/src/CubicHermiteSplineTests.h +23 −0
  354. tests/shared/src/GLMHelpersTests.cpp +74 −0
  355. tests/shared/src/GLMHelpersTests.h +1 −0
  356. tests/shared/src/StorageTests.cpp +9 −7
  357. tools/ac-client/src/ACClientApp.cpp +1 −14
  358. tools/atp-get/src/ATPGetApp.cpp +1 −15
  359. tools/oven/src/BakerCLI.cpp +64 −0
  360. tools/oven/src/BakerCLI.h +34 −0
  361. tools/oven/src/Oven.cpp +30 −7
  362. tools/oven/src/Oven.h +1 −1
  363. unpublishedScripts/marketplace/record/assets/sounds/countdown-tick.wav +0 −0
  364. unpublishedScripts/marketplace/record/assets/sounds/finish-recording.wav +0 −0
  365. unpublishedScripts/marketplace/record/assets/sounds/start-recording.wav +0 −0
  366. unpublishedScripts/marketplace/record/html/css/edit-style.css +1262 −0
  367. unpublishedScripts/marketplace/record/html/css/record.css +0 −0
  368. unpublishedScripts/marketplace/record/html/fonts/FiraSans-SemiBold.ttf +0 −0
  369. unpublishedScripts/marketplace/record/html/fonts/FiraSans.license +94 −0
  370. unpublishedScripts/marketplace/record/html/fonts/Raleway-Bold.ttf +0 −0
  371. unpublishedScripts/marketplace/record/html/fonts/Raleway-Light.ttf +0 −0
  372. unpublishedScripts/marketplace/record/html/fonts/Raleway-Regular.ttf +0 −0
  373. unpublishedScripts/marketplace/record/html/fonts/Raleway-SemiBold.ttf +0 −0
  374. unpublishedScripts/marketplace/record/html/fonts/Raleway.license +94 −0
  375. unpublishedScripts/marketplace/record/html/fonts/hifi-glyphs.ttf +0 −0
  376. unpublishedScripts/marketplace/record/html/img/loader-red-countdown-ring.gif +0 −0
  377. unpublishedScripts/marketplace/record/html/js/record.js +0 −0
  378. unpublishedScripts/marketplace/record/html/record.html +0 −0
  379. unpublishedScripts/marketplace/record/playRecordingAC.js +0 −0
  380. unpublishedScripts/marketplace/record/record.js +0 −0
  381. unpublishedScripts/marketplace/stopwatch/models/transparent-box.fbx +0 −0
  382. unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js +36 −2
  383. unpublishedScripts/marketplace/stopwatch/stopwatchReset.js +22 −0
  384. unpublishedScripts/marketplace/stopwatch/stopwatchServer.js +31 −19
  385. unpublishedScripts/marketplace/stopwatch/stopwatchStartStop.js +7 −6
BUILD.md+15−10
@@ -1,4 +1,4 @@
1−###Dependencies
1+### Dependencies
22
33 * [cmake](https://cmake.org/download/) ~> 3.3.2
44 * [Qt](https://www.qt.io/download-open-source) ~> 5.6.2
@@ -6,7 +6,7 @@ · 1 unmodified line
66 * IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities.
77 * [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
88
9−####CMake External Project Dependencies
9+#### CMake External Project Dependencies
1010
1111 * [boostconfig](https://github.com/boostorg/config) ~> 1.58
1212 * [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases) ~> 2.83
@@ -30,16 +30,19 @@ These are not placed in your normal build tree when doing an out of source build · 17 unmodified lines
3030
3131 If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE_LOCAL_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project.
3232
33−###OS Specific Build Guides
33+### OS Specific Build Guides
34+
3435 * [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X.
3536 * [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux.
3637 * [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows.
3738 * [BUILD_ANDROID.md](BUILD_ANDROID.md) - additional instructions for Android
3839
39−###CMake
40+### CMake
41+
4042 Hifi uses CMake to generate build files and project files for your platform.
4143
42−####Qt
44+#### Qt
45+
4346 In order for CMake to find the Qt5 find modules, you will need to set a QT_CMAKE_PREFIX_PATH environment variable pointing to your Qt installation.
4447
4548 This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
@@ -50,7 +53,8 @@ The path it needs to be set to will depend on where and how Qt5 was installed. e · 4 unmodified lines
5053 export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake
5154 export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
5255
53−####Generating build files
56+#### Generating build files
57+
5458 Create a build directory in the root of your checkout and then run the CMake build from there. This will keep the rest of the directory clean.
5559
5660 mkdir build
@@ -59,14 +63,15 @@ Create a build directory in the root of your checkout and then run the CMake bui · 2 unmodified lines
5963
6064 If cmake gives you the same error message repeatedly after the build fails (e.g. you had a typo in the QT_CMAKE_PREFIX_PATH that you fixed but the `.cmake` files still cannot be found), try removing `CMakeCache.txt`.
6165
62−####Variables
66+#### Variables
67+
6368 Any variables that need to be set for CMake to find dependencies can be set as ENV variables in your shell profile, or passed directly to CMake with a `-D` flag appended to the `cmake ..` command.
6469
6570 For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation:
6671
6772 cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/lib/cmake
6873
69−####Finding Dependencies
74+#### Finding Dependencies
7075
7176 The following applies for dependencies we do not grab via CMake ExternalProject (OpenSSL is an example), or for dependencies you have opted not to grab as a CMake ExternalProject (via -DUSE_LOCAL_$NAME=0). The list of dependencies we grab by default as external projects can be found in [the CMake External Project Dependencies section](#cmake-external-project-dependencies).
7277
@@ -78,8 +83,8 @@ In the examples below the variable $NAME would be replaced by the name of the de · 5 unmodified lines
7883 * $NAME_ROOT_DIR - set this variable in your ENV
7984 * HIFI_LIB_DIR - set this variable in your ENV to your High Fidelity lib folder, should contain a folder '$name'
8085
81−###Optional Components
86+### Optional Components
8287
83−####Devices
88+#### Devices
8489
8590 You can support external input/output devices such as Leap Motion, MIDI, and more by adding each individual SDK in the visible building path. Refer to the readme file available in each device folder in [interface/external/](interface/external) for the detailed explanation of the requirements to use the device.
BUILD_ANDROID.md+9−9
@@ -1,6 +1,6 @@
11 Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Android specific instructions are found in this file.
22
3−###Android Dependencies
3+### Android Dependencies
44
55 You will need the following tools to build our Android targets.
66
@@ -17,23 +17,23 @@ You will need the following tools to build our Android targets. · 10 unmodified lines
1717
1818 You will also need to cross-compile the dependencies required for all platforms for Android, and help CMake find these compiled libraries on your machine.
1919
20−####Scribe
20+#### Scribe
2121
2222 High Fidelity has a shader pre-processing tool called `scribe` that various libraries will call on during the build process. You must compile scribe using your native toolchain (following the build instructions for your platform) and then pass a CMake variable or set an ENV variable `SCRIBE_PATH` that is a path to the scribe executable.
2323
2424 CMake will fatally error if it does not find the scribe executable while using the android toolchain.
2525
26−####Optional Components
26+#### Optional Components
2727
2828 * [Oculus Mobile SDK](https://developer.oculus.com/downloads/#sdk=mobile) ~> 0.4.2
2929
30−####ANDROID_LIB_DIR
30+#### ANDROID_LIB_DIR
3131
3232 Since you won't be installing Android dependencies to system paths on your development machine, CMake will need a little help tracking down your Android dependencies.
3333
3434 This is most easily accomplished by installing all Android dependencies in the same folder. You can place this folder wherever you like on your machine. In this build guide and across our CMakeLists files this folder is referred to as `ANDROID_LIB_DIR`. You can set `ANDROID_LIB_DIR` in your environment or by passing when you run CMake.
3535
36−####Qt
36+#### Qt
3737
3838 Install Qt 5.5.1 for Android for your host environment from the [Qt downloads page](http://www.qt.io/download/). Install Qt to ``$ANDROID_LIB_DIR/Qt``. This is required so that our root CMakeLists file can help CMake find your Android Qt installation.
3939
@@ -41,7 +41,7 @@ The component required for the Android build is the `Android armv7` component. · 1 unmodified line
4141
4242 If you would like to install Qt to a different location, or attempt to build with a different Qt version, you can pass `ANDROID_QT_CMAKE_PREFIX_PATH` to CMake. Point to the `cmake` folder inside `$VERSION_NUMBER/android_armv7/lib`. Otherwise, our root CMakeLists will set it to `$ANDROID_LIB_DIR/Qt/5.5/android_armv7/lib/cmake`.
4343
44−####OpenSSL
44+#### OpenSSL
4545
4646 Cross-compilation of OpenSSL has been tested from an OS X machine running 10.10 compiling OpenSSL 1.0.2. It is likely that the steps below will work for other OpenSSL versions than 1.0.2.
4747
@@ -76,7 +76,7 @@ This should generate libcrypto and libssl in the root of the OpenSSL directory. · 28 unmodified lines
7676
7777 If you have been building other components it is possible that the OpenSSL compile will fail based on the values other cross-compilations (tbb, bullet) have set. Ensure that you are in a new terminal window to avoid compilation errors from previously set environment variables.
7878
79−####Oculus Mobile SDK
79+#### Oculus Mobile SDK
8080
8181 The Oculus Mobile SDK is optional, for Gear VR support. It is not required to compile gvr-interface.
8282
@@ -91,7 +91,7 @@ ndk-build · 8 unmodified lines
9191
9292 This will create the liboculus.a archive that our FindLibOVR module will look for when cmake is run.
9393
94−#####Hybrid testing
94+##### Hybrid testing
9595
9696 Currently the 'vr_dual' mode that would allow us to run a hybrid app has limited support in the Oculus Mobile SDK. The best way to have an application we can launch without having to connect to the GearVR is to put the Gear VR Service into developer mode. This stops Oculus Home from taking over the device when it is plugged into the Gear VR headset, and allows the application to be launched from the Applications page.
9797
@@ -99,7 +99,7 @@ To put the Gear VR Service into developer mode you need an application with an O · 1 unmodified line
9999
100100 Once the application is on your device, go to `Settings->Application Manager->Gear VR Service->Manage Storage`. Tap on `VR Service Version` six times. It will scan your device to verify that you have an osig file in an application on your device, and then it will let you enable Developer mode.
101101
102−###CMake
102+### CMake
103103
104104 We use CMake to generate the makefiles that compile and deploy the Android APKs to your device. In order to create Makefiles for the Android targets, CMake requires that some environment variables are set, and that other variables are passed to it when it is run.
105105
BUILD_LINUX.md+2−1
@@ -1,6 +1,7 @@
11 Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file.
22
3−###Qt5 Dependencies
3+### Qt5 Dependencies
4+
45 Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required:
56
67 libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev
BUILD_OSX.md+7−4
@@ -1,12 +1,13 @@
11 Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file.
22
3−###Homebrew
3+### Homebrew
4+
45 [Homebrew](https://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple.
56
67 brew tap homebrew/versions
78 brew install cmake openssl
89
9−###OpenSSL
10+### OpenSSL
1011
1112 Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations.
1213 For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR:
@@ -15,7 +16,8 @@ For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: · 2 unmodified lines
1516
1617 Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change.
1718
18−###Qt
19+### Qt
20+
1921 Download and install the [Qt 5.6.2 for macOS](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-mac-x64-clang-5.6.2.dmg).
2022
2123 Keep the default components checked when going through the installer.
@@ -23,7 +25,8 @@ Keep the default components checked when going through the installer. · 1 unmodified line
2325 Once Qt is installed, you need to manually configure the following:
2426 * Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.2/5.6/clang_64/lib/cmake/` directory.
2527
26−###Xcode
28+### Xcode
29+
2730 If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles.
2831
2932 cmake .. -GXcode
BUILD_WIN.md+33−29
@@ -1,62 +1,66 @@
11 This is a stand-alone guide for creating your first High Fidelity build for Windows 64-bit.
22
3−###Step 1. Installing Visual Studio 2013
3+## Building High Fidelity
4+
5+### Step 1. Installing Visual Studio 2013
46
57 If you don't already have the Community or Professional edition of Visual Studio 2013, download and install [Visual Studio Community 2013](https://www.visualstudio.com/en-us/news/releasenotes/vs2013-community-vs). You do not need to install any of the optional components when going through the installer.
68
79 Note: Newer versions of Visual Studio are not yet compatible.
810
9−###Step 2. Installing CMake
11+### Step 2. Installing CMake
1012
1113 Download and install the [CMake 3.8.0 win64-x64 Installer](https://cmake.org/files/v3.8/cmake-3.8.0-win64-x64.msi). Make sure "Add CMake to system PATH for all users" is checked when going through the installer.
1214
13−###Step 3. Installing Qt
15+### Step 3. Installing Qt
1416
1517 Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-windows-x86-msvc2013_64-5.6.2.exe).
1618
1719 Keep the default components checked when going through the installer.
1820
19−###Step 4. Setting Qt Environment Variable
21+### Step 4. Setting Qt Environment Variable
2022
21−Go to "Control Panel > System > Advanced System Settings > Environment Variables > New..." (or search “Environment Variables” in Start Search).
22−* Set "Variable name": QT_CMAKE_PREFIX_PATH
23+Go to `Control Panel > System > Advanced System Settings > Environment Variables > New...` (or search “Environment Variables” in Start Search).
24+* Set "Variable name": `QT_CMAKE_PREFIX_PATH`
2325 * Set "Variable value": `%QT_DIR%\5.6\msvc2013_64\lib\cmake`
2426
25−###Step 5. Installing OpenSSL
27+### Step 5. Installing OpenSSL
2628
27−Download and install the [Win64 OpenSSL v1.0.2k Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2k.exe).
29+Download and install the [Win64 OpenSSL v1.0.2L Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2L.exe).
2830
29−###Step 6. Running CMake to Generate Build Files
31+### Step 6. Running CMake to Generate Build Files
3032
3133 Run Command Prompt from Start and run the following commands:
32− cd "%HIFI_DIR%"
33− mkdir build
34− cd build
35− cmake .. -G "Visual Studio 12 Win64"
34+````
35+cd "%HIFI_DIR%"
36+mkdir build
37+cd build
38+cmake .. -G "Visual Studio 12 Win64"
39+````
3640
37−Where %HIFI_DIR% is the directory for the highfidelity repository.
41+Where `%HIFI_DIR%` is the directory for the highfidelity repository.
3842
39−###Step 7. Making a Build
43+### Step 7. Making a Build
4044
41−Open '%HIFI_DIR%\build\hifi.sln' using Visual Studio.
45+Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
4246
4347 Change the Solution Configuration (next to the green play button) from "Debug" to "Release" for best performance.
4448
45−Run Build > Build Solution.
49+Run `Build > Build Solution`.
4650
47−###Step 8. Testing Interface
51+### Step 8. Testing Interface
4852
4953 Create another environment variable (see Step #4)
50−* Set "Variable name": _NO_DEBUG_HEAP
51−* Set "Variable value": 1
54+* Set "Variable name": `_NO_DEBUG_HEAP`
55+* Set "Variable value": `1`
5256
53−In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run Debug > Start Debugging.
57+In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run `Debug > Start Debugging`.
5458
5559 Now, you should have a full build of High Fidelity and be able to run the Interface using Visual Studio. Please check our [Docs](https://wiki.highfidelity.com/wiki/Main_Page) for more information regarding the programming workflow.
5660
57−Note: You can also run Interface by launching it from command line or File Explorer from %HIFI_DIR%\build\interface\Release\interface.exe
61+Note: You can also run Interface by launching it from command line or File Explorer from `%HIFI_DIR%\build\interface\Release\interface.exe`
5862
59−###Troubleshooting
63+## Troubleshooting
6064
6165 For any problems after Step #6, first try this:
6266 * Delete your locally cloned copy of the highfidelity repository
@@ -64,18 +68,18 @@ For any problems after Step #6, first try this: · 1 unmodified line
6468 * Redownload the [repository](https://github.com/highfidelity/hifi)
6569 * Restart directions from Step #6
6670
67−####CMake gives you the same error message repeatedly after the build fails
71+#### CMake gives you the same error message repeatedly after the build fails
6872
69−Remove `CMakeCache.txt` found in the '%HIFI_DIR%\build' directory
73+Remove `CMakeCache.txt` found in the `%HIFI_DIR%\build` directory.
7074
71−####nmake cannot be found
75+#### nmake cannot be found
7276
7377 Make sure nmake.exe is located at the following path:
78+
7479 C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin
7580
7681 If not, add the directory where nmake is located to the PATH environment variable.
7782
78−####Qt is throwing an error
79−
80−Make sure you have the correct version (5.6.2) installed and 'QT_CMAKE_PREFIX_PATH' environment variable is set correctly.
83+#### Qt is throwing an error
8184
85+Make sure you have the correct version (5.6.2) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly.
INSTALL.md+4−4
@@ -2,15 +2,15 @@ Follow the [build guide](BUILD.md) to figure out how to build High Fidelity for · 1 unmodified line
22
33 During generation, CMake should produce an `install` target and a `package` target.
44
5−###Install
5+### Install
66
77 The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`.
88
9−###Packaging
9+### Packaging
1010
1111 To produce an installer, run the `package` target.
1212
13−####Windows
13+#### Windows
1414
1515 To produce an executable installer on Windows, the following are required:
1616
@@ -20,6 +20,6 @@ To produce an executable installer on Windows, the following are required: · 3 unmodified lines
2020
2121 Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.
2222
23−####OS X
23+#### OS X
2424
2525 Run the `package` target to create an Apple Disk Image (.dmg).
assignment-client/src/Agent.cpp+2−5
@@ -166,11 +166,7 @@ void Agent::run() { · 165 unmodified lines
166166
167167 // Setup MessagesClient
168168 auto messagesClient = DependencyManager::set<MessagesClient>();
169− QThread* messagesThread = new QThread;
170− messagesThread->setObjectName("Messages Client Thread");
171− messagesClient->moveToThread(messagesThread);
172− connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
173− messagesThread->start();
169+ messagesClient->startThread();
174170
175171 // make sure we hear about connected nodes so we can grab an ATP script if a request is pending
176172 connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &Agent::nodeActivated);
@@ -410,6 +406,7 @@ void Agent::executeScript() { · 233 unmodified lines
410406 bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened
411407 bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed
412408 _audioGateOpen = audioGateOpen;
409+ Q_UNUSED(openedInLastBlock);
413410
414411 // the codec must be flushed to silence before sending silent packets,
415412 // so delay the transition to silent packets by one packet after becoming silent.
assignment-client/src/AssignmentClient.cpp+1−17
@@ -69,17 +69,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri · 68 unmodified lines
6969 DependencyManager::set<ResourceScriptingInterface>();
7070 DependencyManager::set<UserActivityLoggerScriptingInterface>();
7171
72− // setup a thread for the NodeList and its PacketReceiver
73− QThread* nodeThread = new QThread(this);
74− nodeThread->setObjectName("NodeList Thread");
75− nodeThread->start();
76−
77− // make sure the node thread is given highest priority
78− nodeThread->setPriority(QThread::TimeCriticalPriority);
79−
80− // put the NodeList on the node thread
81− nodeList->moveToThread(nodeThread);
82−
72+ nodeList->startThread();
8373 // set the logging target to the the CHILD_TARGET_NAME
8474 LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
8575
@@ -166,14 +156,8 @@ void AssignmentClient::stopAssignmentClient() { · 80 unmodified lines
166156 }
167157
168158 AssignmentClient::~AssignmentClient() {
169− QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
170−
171159 // remove the NodeList from the DependencyManager
172160 DependencyManager::destroy<NodeList>();
173−
174− // ask the node thread to quit and wait until it is done
175− nodeThread->quit();
176− nodeThread->wait();
177161 }
178162
179163 void AssignmentClient::aboutToQuit() {
assignment-client/src/audio/AudioMixerSlavePool.h+2−2
@@ -16,10 +16,10 @@ · 15 unmodified lines
1616 #include <mutex>
1717 #include <vector>
1818
19−#include <tbb/concurrent_queue.h>
20−
2119 #include <QThread>
2220
21+#include <TBBHelpers.h>
22+
2323 #include "AudioMixerSlave.h"
2424
2525 class AudioMixerSlavePool;
assignment-client/src/avatars/AvatarMixerSlavePool.h+1−2
@@ -16,10 +16,9 @@ · 15 unmodified lines
1616 #include <mutex>
1717 #include <vector>
1818
19−#include <tbb/concurrent_queue.h>
20−
2119 #include <QThread>
2220
21+#include <TBBHelpers.h>
2322 #include <NodeList.h>
2423
2524 #include "AvatarMixerSlave.h"
assignment-client/src/scripts/EntityScriptServer.cpp+1−5
@@ -236,11 +236,7 @@ void EntityScriptServer::run() { · 235 unmodified lines
236236
237237 // Setup MessagesClient
238238 auto messagesClient = DependencyManager::set<MessagesClient>();
239− QThread* messagesThread = new QThread;
240− messagesThread->setObjectName("Messages Client Thread");
241− messagesClient->moveToThread(messagesThread);
242− connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
243− messagesThread->start();
239+ messagesClient->startThread();
244240
245241 DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
246242 connect(&domainHandler, &DomainHandler::settingsReceived, this, &EntityScriptServer::handleSettings);
cmake/externals/nvtt/CMakeLists.txt+4−4
@@ -8,8 +8,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) · 7 unmodified lines
88 if (WIN32)
99 ExternalProject_Add(
1010 ${EXTERNAL_NAME}
11− URL http://s3.amazonaws.com/hifi-public/dependencies/nvtt-win-2.1.0.zip
12− URL_MD5 3ea6eeadbcc69071acf9c49ba565760e
11+ URL http://s3.amazonaws.com/hifi-public/dependencies/nvtt-win-2.1.0.hifi.zip
12+ URL_MD5 10da01cf601f88f6dc12a6bc13c89136
1313 CONFIGURE_COMMAND ""
1414 BUILD_COMMAND ""
1515 INSTALL_COMMAND ""
@@ -29,8 +29,8 @@ else () · 13 unmodified lines
2929
3030 ExternalProject_Add(
3131 ${EXTERNAL_NAME}
32− URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.zip
33− URL_MD5 81b8fa6a9ee3f986088eb6e2215d6a57
32+ URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi.zip
33+ URL_MD5 5794b950f8b265a9a41b2839b3bf7ebb
3434 CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
3535 LOG_DOWNLOAD 1
3636 LOG_CONFIGURE 1
domain-server/resources/web/content/index.shtml+1−1
@@ -21,7 +21,7 @@ · 20 unmodified lines
2121 </p>
2222 <p>
2323 If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:<br>
24− <pre>C:\Users\[username]\AppData\Roaming\High Fidelity\assignment-client/entities/models.json.gz</pre>
24+ <pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
2525 <pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
2626 <pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
2727 </p>
gvr-interface/src/RenderingClient.cpp+2−10
@@ -37,20 +37,12 @@ RenderingClient::RenderingClient(QObject *parent, const QString& launchURLString · 36 unmodified lines
3737 DependencyManager::set<AvatarHashMap>();
3838
3939 // get our audio client setup on its own thread
40− QThread* audioThread = new QThread();
4140 auto audioClient = DependencyManager::set<AudioClient>();
42−
4341 audioClient->setPositionGetter(getPositionForAudio);
4442 audioClient->setOrientationGetter(getOrientationForAudio);
43+ audioClient->startThread();
4544
46− audioClient->moveToThread(audioThread);
47− connect(audioThread, &QThread::started, audioClient.data(), &AudioClient::start);
48− connect(audioClient.data(), &AudioClient::destroyed, audioThread, &QThread::quit);
49− connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
50−
51− audioThread->start();
52−
53−
45+
5446 connect(&_avatarTimer, &QTimer::timeout, this, &RenderingClient::sendAvatarPacket);
5547 _avatarTimer.setInterval(16); // 60 FPS
5648 _avatarTimer.start();
interface/resources/avatar/avatar-animation.json+3−3
@@ -103,8 +103,8 @@ · 102 unmodified lines
103103 "rotationVar": "spine2Rotation",
104104 "typeVar": "spine2Type",
105105 "weightVar": "spine2Weight",
106− "weight": 1.0,
107− "flexCoefficients": [1.0, 0.5, 0.5]
106+ "weight": 2.0,
107+ "flexCoefficients": [1.0, 0.5, 0.25]
108108 },
109109 {
110110 "jointName": "Head",
@@ -113,7 +113,7 @@ · 2 unmodified lines
113113 "typeVar": "headType",
114114 "weightVar": "headWeight",
115115 "weight": 4.0,
116− "flexCoefficients": [1, 0.05, 0.25, 0.25, 0.25]
116+ "flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1]
117117 },
118118 {
119119 "jointName": "LeftArm",
interface/resources/config/render.json+9−7
@@ -1,14 +1,16 @@
11 {
2− "RenderShadowTask": {
3− "Enabled": {
4− "enabled": true
5− }
6− },
7− "RenderDeferredTask": {
8− "AmbientOcclusion": {
2+ "RenderMainView": {
3+ "RenderShadowTask": {
94 "Enabled": {
105 "enabled": true
116 }
7+ },
8+ "RenderDeferredTask": {
9+ "AmbientOcclusion": {
10+ "Enabled": {
11+ "enabled": true
12+ }
13+ }
1214 }
1315 }
1416 }
interface/resources/controllers/oculus_touch.json+3−1
@@ -57,6 +57,8 @@ · 56 unmodified lines
5757 { "from": "OculusTouch.LeftThumbUp", "to": "Standard.LeftThumbUp" },
5858 { "from": "OculusTouch.RightThumbUp", "to": "Standard.RightThumbUp" },
5959 { "from": "OculusTouch.LeftIndexPoint", "to": "Standard.LeftIndexPoint" },
60− { "from": "OculusTouch.RightIndexPoint", "to": "Standard.RightIndexPoint" }
60+ { "from": "OculusTouch.RightIndexPoint", "to": "Standard.RightIndexPoint" },
61+
62+ { "from": "OculusTouch.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] }
6163 ]
6264 }
interface/resources/controllers/vive.json+8−8
@@ -35,35 +35,35 @@ · 34 unmodified lines
3535 { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" },
3636
3737 { "from": "Vive.LeftHand", "to": "Standard.LeftHand", "when": [ "Application.InHMD" ] },
38− { "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] },
38+ { "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] },
3939
4040 {
4141 "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot",
4242 "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}],
43− "when": [ "Application.InHMD"]
43+ "when": [ "Application.InHMD" ]
4444 },
4545
4646 {
4747 "from": "Vive.RightFoot", "to" : "Standard.RightFoot",
4848 "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}],
49− "when": [ "Application.InHMD"]
49+ "when": [ "Application.InHMD" ]
5050 },
5151
5252 {
5353 "from": "Vive.Hips", "to" : "Standard.Hips",
5454 "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}],
55− "when": [ "Application.InHMD"]
55+ "when": [ "Application.InHMD" ]
5656 },
5757
5858 {
5959 "from": "Vive.Spine2", "to" : "Standard.Spine2",
6060 "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}],
61− "when": [ "Application.InHMD"]
61+ "when": [ "Application.InHMD" ]
6262 },
6363
64− { "from": "Vive.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] },
64+ { "from": "Vive.Head", "to" : "Standard.Head", "when": [ "Application.InHMD" ] },
6565
66− { "from": "Vive.RightArm", "to" : "Standard.RightArm", "when" : [ "Application.InHMD"] },
67− { "from": "Vive.LeftArm", "to" : "Standard.LeftArm", "when" : [ "Application.InHMD"] }
66+ { "from": "Vive.RightArm", "to" : "Standard.RightArm", "when": [ "Application.InHMD" ] },
67+ { "from": "Vive.LeftArm", "to" : "Standard.LeftArm", "when": [ "Application.InHMD" ] }
6868 ]
6969 }
interface/resources/fonts/hifi-glyphs.ttf+0−0

Binary file not shown.

interface/resources/html/createGlobalEventBridge.js+1−1
@@ -32,7 +32,7 @@ var EventBridge; · 31 unmodified lines
3232 var webChannel = new QWebChannel(qt.webChannelTransport, function (channel) {
3333 // replace the TempEventBridge with the real one.
3434 var tempEventBridge = EventBridge;
35− EventBridge = channel.objects.eventBridgeWrapper.eventBridge;
35+ EventBridge = channel.objects.eventBridge;
3636 tempEventBridge._callbacks.forEach(function (callback) {
3737 EventBridge.scriptEventReceived.connect(callback);
3838 });
interface/resources/meshes/Jointy3/Jointy3.fbx+0−0

Binary file not shown.

interface/resources/meshes/defaultAvatar_full.fst+73−69
@@ -1,85 +1,89 @@
1−name = Jointy3
1+name = mannequin
22 type = body+head
33 scale = 1
4−filename = Jointy3/Jointy3.fbx
5−texdir = Jointy3/textures
4+filename = mannequin/mannequin.baked.fbx
5+joint = jointEyeLeft = LeftEye
6+joint = jointRightHand = RightHand
7+joint = jointHead = Head
8+joint = jointEyeRight = RightEye
9+joint = jointLean = Spine
610 joint = jointNeck = Neck
711 joint = jointLeftHand = LeftHand
8−joint = jointEyeRight = RightEye
9−joint = jointHead = Head
10−joint = jointRightHand = RightHand
1112 joint = jointRoot = Hips
12−joint = jointLean = Spine
13−joint = jointEyeLeft = LeftEye
1413 freeJoint = LeftArm
1514 freeJoint = LeftForeArm
1615 freeJoint = RightArm
1716 freeJoint = RightForeArm
18−jointIndex = RightHand = 17
19−jointIndex = LeftHandIndex3 = 56
20−jointIndex = Hips = 0
21−jointIndex = LeftHandRing2 = 47
22−jointIndex = LeftHandThumb3 = 60
23−jointIndex = RightShoulder = 14
24−jointIndex = RightHandRing1 = 30
25−jointIndex = RightHandRing3 = 32
26−jointIndex = LeftHandPinky4 = 45
27−jointIndex = LeftHandRing1 = 46
28−jointIndex = LeftFoot = 8
29−jointIndex = RightHandIndex2 = 23
30−jointIndex = RightToeBase = 4
31−jointIndex = RightHandMiddle4 = 29
32−jointIndex = RightHandPinky4 = 37
33−jointIndex = LeftToe_End = 10
34−jointIndex = RightEye = 66
35−jointIndex = RightHandPinky2 = 35
36−jointIndex = RightHandRing2 = 31
17+bs = EyeBlink_L = blink = 1
18+bs = JawOpen = mouth_Open = 1
19+bs = LipsFunnel = Oo = 1
20+bs = BrowsU_L = brow_Up = 1
21+jointIndex = RightHandIndex2 = 27
22+jointIndex = LeftHandIndex2 = 51
23+jointIndex = RightUpLeg = 6
24+jointIndex = RightToe_End = 10
25+jointIndex = RightEye = 65
26+jointIndex = LeftHandPinky1 = 42
27+jointIndex = RightHandRing1 = 22
28+jointIndex = face = 67
29+jointIndex = LeftUpLeg = 1
3730 jointIndex = LeftHand = 41
38−jointIndex = RightToe_End = 5
39−jointIndex = LeftEye = 65
40−jointIndex = LeftHandThumb2 = 59
41−jointIndex = pCylinder73Shape1 = 67
31+jointIndex = LeftHandMiddle1 = 58
32+jointIndex = LeftHandIndex1 = 50
33+jointIndex = LeftEye = 64
34+jointIndex = RightHandIndex1 = 26
35+jointIndex = LeftHandPinky4 = 45
36+jointIndex = RightArm = 15
4237 jointIndex = LeftShoulder = 38
43−jointIndex = LeftHandIndex2 = 55
38+jointIndex = RightHandPinky2 = 19
39+jointIndex = RightHandThumb1 = 30
4440 jointIndex = RightForeArm = 16
45−jointIndex = LeftHandMiddle2 = 51
46−jointIndex = RightHandRing4 = 33
47−jointIndex = LeftLeg = 7
48−jointIndex = LeftHandThumb4 = 61
49−jointIndex = LeftForeArm = 40
50−jointIndex = HeadTop_End = 64
51−jointIndex = RightHandPinky1 = 34
52−jointIndex = RightHandIndex1 = 22
53−jointIndex = LeftHandIndex1 = 54
54−jointIndex = RightLeg = 2
55−jointIndex = RightHandIndex4 = 25
41+jointIndex = LeftHandMiddle3 = 60
5642 jointIndex = Neck = 62
57−jointIndex = LeftHandMiddle1 = 50
58−jointIndex = RightHandPinky3 = 36
59−jointIndex = LeftHandPinky2 = 43
60−jointIndex = RightHandMiddle3 = 28
61−jointIndex = RightHandThumb4 = 21
62−jointIndex = LeftUpLeg = 6
63−jointIndex = RightFoot = 3
64−jointIndex = LeftHandThumb1 = 58
65−jointIndex = LeftArm = 39
66−jointIndex = RightHandMiddle1 = 26
67−jointIndex = LeftHandRing3 = 48
68−jointIndex = LeftHandMiddle4 = 53
69−jointIndex = RightUpLeg = 1
70−jointIndex = RightHandMiddle2 = 27
71−jointIndex = LeftToeBase = 9
72−jointIndex = RightHandThumb2 = 19
43+jointIndex = LeftHandThumb1 = 54
44+jointIndex = RightHandMiddle2 = 35
45+jointIndex = LeftHandMiddle4 = 61
46+jointIndex = mannequin = 68
47+jointIndex = Spine1 = 12
48+jointIndex = RightFoot = 8
49+jointIndex = RightHand = 17
50+jointIndex = LeftHandIndex3 = 52
51+jointIndex = RightHandIndex3 = 28
52+jointIndex = RightHandMiddle4 = 37
53+jointIndex = LeftLeg = 2
54+jointIndex = RightHandMiddle1 = 34
7355 jointIndex = Spine2 = 13
74−jointIndex = Spine = 11
56+jointIndex = LeftHandMiddle2 = 59
57+jointIndex = LeftHandPinky3 = 44
58+jointIndex = LeftHandThumb3 = 56
7559 jointIndex = LeftHandRing4 = 49
60+jointIndex = RightHandThumb2 = 31
61+jointIndex = LeftHandRing3 = 48
62+jointIndex = HeadTop_End = 66
63+jointIndex = LeftHandThumb4 = 57
64+jointIndex = RightHandThumb3 = 32
65+jointIndex = RightHandPinky1 = 18
66+jointIndex = RightLeg = 7
67+jointIndex = RightHandMiddle3 = 36
68+jointIndex = RightHandPinky3 = 20
69+jointIndex = LeftToeBase = 4
70+jointIndex = LeftForeArm = 40
71+jointIndex = RightShoulder = 14
72+jointIndex = LeftHandRing2 = 47
73+jointIndex = LeftHandThumb2 = 55
7674 jointIndex = Head = 63
77−jointIndex = LeftHandPinky3 = 44
78−jointIndex = LeftHandPinky1 = 42
79−jointIndex = RightHandThumb1 = 18
80−jointIndex = LeftHandIndex4 = 57
81−jointIndex = LeftHandMiddle3 = 52
82−jointIndex = RightHandIndex3 = 24
83−jointIndex = Spine1 = 12
84−jointIndex = RightArm = 15
85−jointIndex = RightHandThumb3 = 20
75+jointIndex = RightHandRing4 = 25
76+jointIndex = LeftHandRing1 = 46
77+jointIndex = LeftFoot = 3
78+jointIndex = RightHandRing3 = 24
79+jointIndex = RightHandThumb4 = 33
80+jointIndex = LeftArm = 39
81+jointIndex = LeftToe_End = 5
82+jointIndex = RightToeBase = 9
83+jointIndex = RightHandPinky4 = 21
84+jointIndex = Spine = 11
85+jointIndex = LeftHandIndex4 = 53
86+jointIndex = LeftHandPinky2 = 43
87+jointIndex = RightHandIndex4 = 29
88+jointIndex = Hips = 0
89+jointIndex = RightHandRing2 = 23
interface/resources/meshes/mannequin/Eyes.ktx+0−0

Binary file not shown.

interface/resources/meshes/mannequin/lambert1_Base_Color.ktx+0−0

Binary file not shown.

interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx+0−0

Binary file not shown.

interface/resources/meshes/mannequin/lambert1_Roughness.ktx+0−0

Binary file not shown.

interface/resources/meshes/mannequin/mannequin.baked.fbx+0−0

Binary file not shown.

interface/resources/qml/AssetServer.qml+1−1
@@ -177,7 +177,7 @@ ScrollingWindow { · 176 unmodified lines
177177 SHAPE_TYPE_STATIC_MESH
178178 ],
179179 checkStateOnDisable: false,
180− warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic"
180+ warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors"
181181 }
182182 });
183183
interface/resources/qml/Browser.qml+3−18
@@ -2,7 +2,6 @@ import QtQuick 2.5 · 1 unmodified line
22 import QtQuick.Controls 1.2
33 import QtWebChannel 1.0
44 import QtWebEngine 1.2
5−import FileTypeProfile 1.0
65
76 import "controls-uit"
87 import "styles" as HifiStyles
@@ -22,8 +21,6 @@ ScrollingWindow { · 13 unmodified lines
2221 property alias url: webview.url
2322 property alias webView: webview
2423
25− property alias eventBridge: eventBridgeWrapper.eventBridge
26−
2724 signal loadingChanged(int status)
2825
2926 x: 100
@@ -209,21 +206,7 @@ ScrollingWindow { · 179 unmodified lines
209206 WebView {
210207 id: webview
211208 url: "https://highfidelity.com/"
212−
213− property alias eventBridgeWrapper: eventBridgeWrapper
214−
215− QtObject {
216− id: eventBridgeWrapper
217− WebChannel.id: "eventBridgeWrapper"
218− property var eventBridge;
219− }
220−
221− profile: FileTypeProfile {
222− id: webviewProfile
223− storageName: "qmlWebEngine"
224− }
225−
226− webChannel.registeredObjects: [eventBridgeWrapper]
209+ profile: FileTypeProfile;
227210
228211 // Create a global EventBridge object for raiseAndLowerKeyboard.
229212 WebEngineScript {
@@ -271,6 +254,8 @@ ScrollingWindow { · 41 unmodified lines
271254 }
272255
273256 Component.onCompleted: {
257+ webChannel.registerObject("eventBridge", eventBridge);
258+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
274259 desktop.initWebviewProfileHandlers(webview.profile);
275260 }
276261 }
interface/resources/qml/QmlWebWindow.qml+2−8
@@ -26,15 +26,8 @@ Windows.ScrollingWindow { · 25 unmodified lines
2626 // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
2727 destroyOnCloseButton: false
2828 property alias source: webview.url
29− property alias eventBridge: eventBridgeWrapper.eventBridge;
3029 property alias scriptUrl: webview.userScriptUrl
3130
32− QtObject {
33− id: eventBridgeWrapper
34− WebChannel.id: "eventBridgeWrapper"
35− property var eventBridge;
36− }
37−
3831 // This is for JS/QML communication, which is unused in a WebWindow,
3932 // but not having this here results in spurious warnings about a
4033 // missing signal
@@ -70,7 +63,6 @@ Windows.ScrollingWindow { · 29 unmodified lines
7063 url: "about:blank"
7164 anchors.fill: parent
7265 focus: true
73− webChannel.registeredObjects: [eventBridgeWrapper]
7466
7567 property string userScriptUrl: ""
7668
@@ -107,6 +99,8 @@ Windows.ScrollingWindow { · 30 unmodified lines
10799 }
108100
109101 Component.onCompleted: {
102+ webChannel.registerObject("eventBridge", eventBridge);
103+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
110104 eventBridge.webEventReceived.connect(onWebEventReceived);
111105 }
112106 }
interface/resources/qml/QmlWindow.qml+0−9
@@ -30,15 +30,6 @@ Windows.Window { · 29 unmodified lines
3030 property bool keyboardRaised: false
3131 property bool punctuationMode: false
3232
33− // JavaScript event bridge object in case QML content includes Web content.
34− property alias eventBridge: eventBridgeWrapper.eventBridge;
35−
36− QtObject {
37− id: eventBridgeWrapper
38− WebChannel.id: "eventBridgeWrapper"
39− property var eventBridge;
40− }
41−
4233 onSourceChanged: {
4334 if (dynamicContent) {
4435 dynamicContent.destroy();
interface/resources/qml/TabletBrowser.qml+4−14
@@ -8,7 +8,6 @@ import "controls-uit" as HifiControls · 7 unmodified lines
88 import "styles" as HifiStyles
99 import "styles-uit"
1010 import "windows"
11−import HFTabletWebEngineProfile 1.0
1211
1312 Item {
1413 id: root
@@ -19,7 +18,6 @@ Item { · 4 unmodified lines
1918 property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
2019 property alias url: webview.url
2120 property WebEngineView webView: webview
22− property alias eventBridge: eventBridgeWrapper.eventBridge
2321 property bool canGoBack: webview.canGoBack
2422 property bool canGoForward: webview.canGoForward
2523
@@ -33,12 +31,6 @@ Item { · 7 unmodified lines
3331 webview.profile = profile;
3432 }
3533
36− QtObject {
37− id: eventBridgeWrapper
38− WebChannel.id: "eventBridgeWrapper"
39− property var eventBridge;
40− }
41−
4234 WebEngineView {
4335 id: webview
4436 objectName: "webEngineView"
@@ -47,10 +39,7 @@ Item { · 2 unmodified lines
4739 width: parent.width
4840 height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
4941
50− profile: HFTabletWebEngineProfile {
51− id: webviewTabletProfile
52− storageName: "qmlTabletWebEngine"
53− }
42+ profile: HFTabletWebEngineProfile;
5443
5544 property string userScriptUrl: ""
5645
@@ -82,9 +71,10 @@ Item { · 25 unmodified lines
8271
8372 property string newUrl: ""
8473
85− webChannel.registeredObjects: [eventBridgeWrapper]
86−
8774 Component.onCompleted: {
75+ webChannel.registerObject("eventBridge", eventBridge);
76+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
77+
8878 // Ensure the JS from the web-engine makes it to our logging
8979 webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
9080 console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
interface/resources/qml/ToolWindow.qml+3−10
@@ -79,15 +79,11 @@ ScrollingWindow { · 78 unmodified lines
7979 id: webView
8080 anchors.fill: parent
8181 enabled: false
82− property alias eventBridgeWrapper: eventBridgeWrapper
83−
84− QtObject {
85− id: eventBridgeWrapper
86− WebChannel.id: "eventBridgeWrapper"
87− property var eventBridge
82+ Component.onCompleted: {
83+ webChannel.registerObject("eventBridge", eventBridge);
84+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
8885 }
8986
90− webChannel.registeredObjects: [eventBridgeWrapper]
9187 onEnabledChanged: toolWindow.updateVisiblity()
9288 }
9389 }
@@ -251,12 +247,9 @@ ScrollingWindow { · 157 unmodified lines
251247 tab.enabled = true;
252248 tab.originalUrl = properties.source;
253249
254− var eventBridge = properties.eventBridge;
255−
256250 var result = tab.item;
257251 result.enabled = true;
258252 tabView.tabCount++;
259− result.eventBridgeWrapper.eventBridge = eventBridge;
260253 result.url = properties.source;
261254 return result;
262255 }
interface/resources/qml/controls-uit/BaseWebView.qml+0−3
@@ -10,13 +10,10 @@ · 9 unmodified lines
1010
1111 import QtQuick 2.5
1212 import QtWebEngine 1.2
13−import HFWebEngineProfile 1.0
1413
1514 WebEngineView {
1615 id: root
1716
18− // profile: desktop.browserProfile
19−
2017 Component.onCompleted: {
2118 console.log("Connecting JS messaging to Hifi Logging")
2219 // Ensure the JS from the web-engine makes it to our logging
interface/resources/qml/controls/TabletWebScreen.qml+4−14
@@ -2,12 +2,10 @@ import QtQuick 2.5 · 1 unmodified line
22 import QtWebEngine 1.1
33 import QtWebChannel 1.0
44 import "../controls-uit" as HiFiControls
5−import HFTabletWebEngineProfile 1.0
65
76 Item {
87 property alias url: root.url
98 property alias scriptURL: root.userScriptUrl
10− property alias eventBridge: eventBridgeWrapper.eventBridge
119 property alias canGoBack: root.canGoBack;
1210 property var goBack: root.goBack;
1311 property alias urlTag: root.urlTag
@@ -23,12 +21,6 @@ Item { · 9 unmodified lines
2321 }
2422 */
2523
26− QtObject {
27− id: eventBridgeWrapper
28− WebChannel.id: "eventBridgeWrapper"
29− property var eventBridge;
30− }
31−
3224 property alias viewProfile: root.profile
3325
3426 WebEngineView {
@@ -39,10 +31,7 @@ Item { · 4 unmodified lines
3931 width: parent.width
4032 height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
4133
42− profile: HFTabletWebEngineProfile {
43− id: webviewProfile
44− storageName: "qmlTabletWebEngine"
45− }
34+ profile: HFTabletWebEngineProfile;
4635
4736 property string userScriptUrl: ""
4837
@@ -75,10 +64,11 @@ Item { · 26 unmodified lines
7564 userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
7665
7766 property string newUrl: ""
78−
79− webChannel.registeredObjects: [eventBridgeWrapper]
67+
8068
8169 Component.onCompleted: {
70+ webChannel.registerObject("eventBridge", eventBridge);
71+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
8272 // Ensure the JS from the web-engine makes it to our logging
8373 root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
8474 console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
interface/resources/qml/controls/TabletWebView.qml+3−14
@@ -2,7 +2,6 @@ import QtQuick 2.5 · 1 unmodified line
22 import QtQuick.Controls 1.4
33 import QtWebEngine 1.2
44 import QtWebChannel 1.0
5−import HFTabletWebEngineProfile 1.0
65 import "../controls-uit" as HiFiControls
76 import "../styles" as HifiStyles
87 import "../styles-uit"
@@ -18,7 +17,6 @@ Item { · 9 unmodified lines
1817 property int headerHeight: 70
1918 property string url
2019 property string scriptURL
21− property alias eventBridge: eventBridgeWrapper.eventBridge
2220 property bool keyboardEnabled: false
2321 property bool keyboardRaised: false
2422 property bool punctuationMode: false
@@ -136,12 +134,6 @@ Item { · 111 unmodified lines
136134 loadUrl(url);
137135 }
138136
139− QtObject {
140− id: eventBridgeWrapper
141− WebChannel.id: "eventBridgeWrapper"
142− property var eventBridge;
143− }
144−
145137 WebEngineView {
146138 id: webview
147139 objectName: "webEngineView"
@@ -150,10 +142,7 @@ Item { · 2 unmodified lines
150142 width: parent.width
151143 height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height - web.headerHeight : parent.height - web.headerHeight
152144 anchors.top: buttons.bottom
153− profile: HFTabletWebEngineProfile {
154− id: webviewTabletProfile
155− storageName: "qmlTabletWebEngine"
156− }
145+ profile: HFTabletWebEngineProfile;
157146
158147 property string userScriptUrl: ""
159148
@@ -186,9 +175,9 @@ Item { · 26 unmodified lines
186175
187176 property string newUrl: ""
188177
189− webChannel.registeredObjects: [eventBridgeWrapper]
190−
191178 Component.onCompleted: {
179+ webChannel.registerObject("eventBridge", eventBridge);
180+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
192181 // Ensure the JS from the web-engine makes it to our logging
193182 webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
194183 console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
interface/resources/qml/controls/WebEntityView.qml+1−5
@@ -10,13 +10,9 @@ · 9 unmodified lines
1010
1111 import QtQuick 2.5
1212 import "."
13−import FileTypeProfile 1.0
1413
1514 WebView {
16− viewProfile: FileTypeProfile {
17− id: webviewProfile
18− storageName: "qmlWebEngine"
19− }
15+ viewProfile: FileTypeProfile;
2016
2117 urlTag: "noDownload=true";
2218 }
interface/resources/qml/controls/WebView.qml+3−14
@@ -2,12 +2,10 @@ import QtQuick 2.5 · 1 unmodified line
22 import QtWebEngine 1.1
33 import QtWebChannel 1.0
44 import "../controls-uit" as HiFiControls
5−import HFWebEngineProfile 1.0
65
76 Item {
87 property alias url: root.url
98 property alias scriptURL: root.userScriptUrl
10− property alias eventBridge: eventBridgeWrapper.eventBridge
119 property alias canGoBack: root.canGoBack;
1210 property var goBack: root.goBack;
1311 property alias urlTag: root.urlTag
@@ -23,12 +21,6 @@ Item { · 9 unmodified lines
2321 }
2422 */
2523
26− QtObject {
27− id: eventBridgeWrapper
28− WebChannel.id: "eventBridgeWrapper"
29− property var eventBridge;
30− }
31−
3224 property alias viewProfile: root.profile
3325
3426 WebEngineView {
@@ -39,10 +31,7 @@ Item { · 4 unmodified lines
3931 width: parent.width
4032 height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
4133
42− profile: HFWebEngineProfile {
43− id: webviewProfile
44− storageName: "qmlWebEngine"
45− }
34+ profile: HFWebEngineProfile;
4635
4736 property string userScriptUrl: ""
4837
@@ -76,9 +65,9 @@ Item { · 27 unmodified lines
7665
7766 property string newUrl: ""
7867
79− webChannel.registeredObjects: [eventBridgeWrapper]
80−
8168 Component.onCompleted: {
69+ webChannel.registerObject("eventBridge", eventBridge);
70+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
8271 // Ensure the JS from the web-engine makes it to our logging
8372 root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
8473 console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
interface/resources/qml/dialogs/TabletLoginDialog.qml+0−1
@@ -20,7 +20,6 @@ TabletModalWindow { · 19 unmodified lines
2020 id: loginDialogRoot
2121 objectName: "LoginDialog"
2222
23− property var eventBridge;
2423 signal sendToScript(var message);
2524 property bool isHMD: false
2625 property bool gotoPreviousApp: false;
interface/resources/qml/dialogs/preferences/AvatarBrowser.qml+4−12
@@ -24,8 +24,6 @@ Window { · 23 unmodified lines
2424 resizable: true
2525 modality: Qt.ApplicationModal
2626
27− property alias eventBridge: eventBridgeWrapper.eventBridge
28−
2927 Item {
3028 anchors.fill: parent
3129
@@ -45,16 +43,6 @@ Window { · 13 unmodified lines
4543 bottom: keyboard.top
4644 }
4745
48− property alias eventBridgeWrapper: eventBridgeWrapper
49−
50− QtObject {
51− id: eventBridgeWrapper
52− WebChannel.id: "eventBridgeWrapper"
53− property var eventBridge;
54− }
55−
56− webChannel.registeredObjects: [eventBridgeWrapper]
57−
5846 // Create a global EventBridge object for raiseAndLowerKeyboard.
5947 WebEngineScript {
6048 id: createGlobalEventBridge
@@ -73,6 +61,10 @@ Window { · 12 unmodified lines
7361
7462 userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
7563
64+ Component.onCompleted: {
65+ webChannel.registerObject("eventBridge", eventBridge);
66+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
67+ }
7668 }
7769
7870 Keyboard {
interface/resources/qml/dialogs/preferences/AvatarPreference.qml+1−3
@@ -116,9 +116,7 @@ Preference { · 115 unmodified lines
116116
117117 Component {
118118 id: tabletAvatarBrowserBuilder;
119− TabletAvatarBrowser {
120− eventBridge: tabletRoot.eventBridge
121− }
119+ TabletAvatarBrowser { }
122120 }
123121
124122 }
interface/resources/qml/hifi/Desktop.qml+0−6
@@ -2,7 +2,6 @@ import QtQuick 2.5 · 1 unmodified line
22 import QtQuick.Controls 1.4
33 import QtWebEngine 1.1;
44 import Qt.labs.settings 1.0
5−import HFWebEngineProfile 1.0
65
76 import "../desktop" as OriginalDesktop
87 import ".."
@@ -27,11 +26,6 @@ OriginalDesktop.Desktop { · 18 unmodified lines
2726 property alias toolWindow: toolWindow
2827 ToolWindow { id: toolWindow }
2928
30− property var browserProfile: HFWebEngineProfile {
31− id: webviewProfile
32− storageName: "qmlWebEngine"
33− }
34−
3529 Action {
3630 text: "Open Browser"
3731 shortcut: "Ctrl+B"
interface/resources/qml/hifi/Pal.qml+7−4
@@ -44,7 +44,6 @@ Rectangle { · 43 unmodified lines
4444 property var activeTab: "nearbyTab";
4545 property bool currentlyEditingDisplayName: false
4646 property bool punctuationMode: false;
47− property var eventBridge;
4847
4948 HifiConstants { id: hifi; }
5049
@@ -388,8 +387,13 @@ Rectangle { · 337 unmodified lines
388387 sortIndicatorColumn: settings.nearbySortIndicatorColumn;
389388 sortIndicatorOrder: settings.nearbySortIndicatorOrder;
390389 onSortIndicatorColumnChanged: {
391− settings.nearbySortIndicatorColumn = sortIndicatorColumn;
392− sortModel();
390+ if (sortIndicatorColumn > 2) {
391+ // these are not sortable, switch back to last column
392+ sortIndicatorColumn = settings.nearbySortIndicatorColumn;
393+ } else {
394+ settings.nearbySortIndicatorColumn = sortIndicatorColumn;
395+ sortModel();
396+ }
393397 }
394398 onSortIndicatorOrderChanged: {
395399 settings.nearbySortIndicatorOrder = sortIndicatorOrder;
@@ -1038,7 +1042,6 @@ Rectangle { · 642 unmodified lines
10381042 } // Keyboard
10391043
10401044 HifiControls.TabletWebView {
1041− eventBridge: pal.eventBridge;
10421045 id: userInfoViewer;
10431046 anchors {
10441047 top: parent.top;
interface/resources/qml/hifi/dialogs/TabletAssetServer.qml+1−2
@@ -24,7 +24,6 @@ Rectangle { · 23 unmodified lines
2424 property string title: "Asset Browser"
2525 property bool keyboardRaised: false
2626
27− property var eventBridge;
2827 signal sendToScript(var message);
2928 property bool isHMD: false
3029
@@ -179,7 +178,7 @@ Rectangle { · 148 unmodified lines
179178 SHAPE_TYPE_STATIC_MESH
180179 ],
181180 checkStateOnDisable: false,
182− warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic"
181+ warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors"
183182 }
184183 });
185184
interface/resources/qml/hifi/dialogs/TabletDCDialog.qml+0−1
@@ -20,7 +20,6 @@ Rectangle { · 19 unmodified lines
2020 id: root
2121 objectName: "DCConectionTiming"
2222
23− property var eventBridge;
2423 signal sendToScript(var message);
2524 property bool isHMD: false
2625
interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml+0−1
@@ -19,7 +19,6 @@ Rectangle { · 18 unmodified lines
1919 id: root
2020
2121 objectName: "DebugWindow"
22− property var eventBridge;
2322
2423 property var title: "Debug Window"
2524 property bool isHMD: false
interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml+0−1
@@ -20,7 +20,6 @@ Rectangle { · 19 unmodified lines
2020 id: root
2121 objectName: "EntityStatistics"
2222
23− property var eventBridge;
2423 signal sendToScript(var message);
2524 property bool isHMD: false
2625
interface/resources/qml/hifi/dialogs/TabletLODTools.qml+0−1
@@ -20,7 +20,6 @@ Rectangle { · 19 unmodified lines
2020 id: root
2121 objectName: "LODTools"
2222
23− property var eventBridge;
2423 signal sendToScript(var message);
2524 property bool isHMD: false
2625
interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml+0−1
@@ -23,7 +23,6 @@ Rectangle { · 22 unmodified lines
2323 property string title: "Running Scripts"
2424 HifiConstants { id: hifi }
2525 signal sendToScript(var message);
26− property var eventBridge;
2726 property var scripts: ScriptDiscoveryService;
2827 property var scriptsModel: scripts.scriptsModelFilter
2928 property var runningScriptsModel: ListModel { }
interface/resources/qml/hifi/tablet/Edit.qml+0−2
@@ -7,14 +7,12 @@ StackView { · 6 unmodified lines
77 objectName: "stack"
88 initialItem: Qt.resolvedUrl('EditTabView.qml')
99
10− property var eventBridge;
1110 signal sendToScript(var message);
1211
1312 HifiConstants { id: hifi }
1413
1514 function pushSource(path) {
1615 editRoot.push(Qt.resolvedUrl(path));
17− editRoot.currentItem.eventBridge = editRoot.eventBridge;
1816 editRoot.currentItem.sendToScript.connect(editRoot.sendToScript);
1917 }
2018
interface/resources/qml/hifi/tablet/EditTabView.qml+1−6
@@ -5,7 +5,6 @@ import QtWebChannel 1.0 · 4 unmodified lines
55 import QtQuick.Controls.Styles 1.4
66 import "../../controls"
77 import "../toolbars"
8−import HFWebEngineProfile 1.0
98 import QtGraphicalEffects 1.0
109 import "../../controls-uit" as HifiControls
1110 import "../../styles-uit"
@@ -182,7 +181,6 @@ TabView { · 170 unmodified lines
182181 WebView {
183182 id: entityListToolWebView
184183 url: "../../../../../scripts/system/html/entityList.html"
185− eventBridge: editRoot.eventBridge
186184 anchors.fill: parent
187185 enabled: true
188186 }
@@ -197,7 +195,6 @@ TabView { · 8 unmodified lines
197195 WebView {
198196 id: entityPropertiesWebView
199197 url: "../../../../../scripts/system/html/entityProperties.html"
200− eventBridge: editRoot.eventBridge
201198 anchors.fill: parent
202199 enabled: true
203200 }
@@ -212,7 +209,6 @@ TabView { · 8 unmodified lines
212209 WebView {
213210 id: gridControlsWebView
214211 url: "../../../../../scripts/system/html/gridControls.html"
215− eventBridge: editRoot.eventBridge
216212 anchors.fill: parent
217213 enabled: true
218214 }
@@ -227,7 +223,6 @@ TabView { · 8 unmodified lines
227223 WebView {
228224 id: particleExplorerWebView
229225 url: "../../../../../scripts/system/particle_explorer/particleExplorer.html"
230− eventBridge: editRoot.eventBridge
231226 anchors.fill: parent
232227 enabled: true
233228 }
@@ -290,7 +285,7 @@ TabView { · 56 unmodified lines
290285 editTabView.currentIndex = id;
291286 } else {
292287 console.warn('Attempt to switch to invalid tab:', id);
293− }
288+ }
294289 } else if (typeof id === 'string'){
295290 switch (id.toLowerCase()) {
296291 case 'create':
interface/resources/qml/hifi/tablet/InputRecorder.qml+0−1
@@ -18,7 +18,6 @@ import "../../dialogs" · 17 unmodified lines
1818
1919 Rectangle {
2020 id: inputRecorder
21− property var eventBridge;
2221 HifiConstants { id: hifi }
2322 signal sendToScript(var message);
2423 color: hifi.colors.baseGray;
interface/resources/qml/hifi/tablet/NewModelDialog.qml+1−2
@@ -20,7 +20,6 @@ Rectangle { · 19 unmodified lines
2020 // height: parent.height
2121 HifiConstants { id: hifi }
2222 color: hifi.colors.baseGray;
23− property var eventBridge;
2423 signal sendToScript(var message);
2524 property bool keyboardEnabled: false
2625 property bool punctuationMode: false
@@ -118,7 +117,7 @@ Rectangle { · 91 unmodified lines
118117 id: text2
119118 width: 160
120119 color: "#ffffff"
121− text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic")
120+ text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors")
122121 wrapMode: Text.WordWrap
123122 font.pixelSize: 12
124123 }
interface/resources/qml/hifi/tablet/TabletAddressDialog.qml+0−2
@@ -29,7 +29,6 @@ StackView { · 28 unmodified lines
2929 initialItem: addressBarDialog
3030 width: parent !== null ? parent.width : undefined
3131 height: parent !== null ? parent.height : undefined
32− property var eventBridge;
3332 property int cardWidth: 212;
3433 property int cardHeight: 152;
3534 property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
@@ -80,7 +79,6 @@ StackView { · 44 unmodified lines
8079 var card = tabletWebView.createObject();
8180 card.url = addressBarDialog.metaverseServerUrl + targetString;
8281 card.parentStackItem = root;
83− card.eventBridge = root.eventBridge;
8482 root.push(card);
8583 return;
8684 }
interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml+0−1
@@ -25,7 +25,6 @@ Item { · 24 unmodified lines
2525 property bool keyboardRaised: false
2626 property bool punctuationMode: false
2727
28− property var eventBridge;
2928 signal sendToScript(var message);
3029
3130 anchors.fill: parent
interface/resources/qml/hifi/tablet/TabletAudioBuffers.qml+1−0
@@ -20,6 +20,7 @@ StackView { · 19 unmodified lines
2020 property string title: "Audio Buffers"
2121 property alias gotoPreviousApp: root.gotoPreviousApp;
2222 property var eventBridge;
23+
2324 signal sendToScript(var message);
2425
2526 function pushSource(path) {
interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml+0−1
@@ -19,7 +19,6 @@ StackView { · 18 unmodified lines
1919 objectName: "stack"
2020 property string title: "Avatar Settings"
2121
22− property var eventBridge;
2322 signal sendToScript(var message);
2423
2524 function pushSource(path) {
interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml+0−1
@@ -19,7 +19,6 @@ StackView { · 18 unmodified lines
1919 objectName: "stack"
2020 property string title: "General Settings"
2121 property alias gotoPreviousApp: root.gotoPreviousApp;
22− property var eventBridge;
2322 signal sendToScript(var message);
2423
2524 function pushSource(path) {
interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml+0−1
@@ -19,7 +19,6 @@ StackView { · 18 unmodified lines
1919 objectName: "stack"
2020 property string title: "Graphics Settings"
2121
22− property var eventBridge;
2322 signal sendToScript(var message);
2423
2524 function pushSource(path) {
interface/resources/qml/hifi/tablet/TabletLodPreferences.qml+0−1
@@ -19,7 +19,6 @@ StackView { · 18 unmodified lines
1919 objectName: "stack"
2020 property string title: "LOD Settings"
2121
22− property var eventBridge;
2322 signal sendToScript(var message);
2423
2524 function pushSource(path) {
interface/resources/qml/hifi/tablet/TabletMenu.qml+0−2
@@ -4,7 +4,6 @@ import QtQuick.Controls 1.4 · 3 unmodified lines
44 import QtQml 2.2
55 import QtWebChannel 1.0
66 import QtWebEngine 1.1
7−import HFWebEngineProfile 1.0
87
98
109 import "."
@@ -22,7 +21,6 @@ FocusScope { · 11 unmodified lines
2221 property var point: Qt.point(50, 50);
2322 TabletMenuStack { id: menuPopperUpper }
2423 property string subMenu: ""
25− property var eventBridge;
2624 signal sendToScript(var message);
2725
2826 Rectangle {
interface/resources/qml/hifi/tablet/TabletMenuStack.qml+0−1
@@ -49,7 +49,6 @@ Item { · 48 unmodified lines
4949
5050 function pushSource(path) {
5151 d.push(Qt.resolvedUrl(path));
52− d.currentItem.eventBridge = tabletMenu.eventBridge
5352 d.currentItem.sendToScript.connect(tabletMenu.sendToScript);
5453 d.currentItem.focus = true;
5554 d.currentItem.forceActiveFocus();
interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml+0−1
@@ -19,7 +19,6 @@ StackView { · 18 unmodified lines
1919 objectName: "stack"
2020 property var title: "Networking Settings"
2121
22− property var eventBridge;
2322 signal sendToScript(var message);
2423
2524 function pushSource(path) {
interface/resources/qml/hifi/tablet/TabletRoot.qml+11−14
@@ -1,7 +1,6 @@
11 import QtQuick 2.0
22 import Hifi 1.0
33 import QtQuick.Controls 1.4
4−import HFTabletWebEngineProfile 1.0
54 import "../../dialogs"
65 import "../../controls"
76
@@ -9,7 +8,6 @@ Item { · 1 unmodified line
98 id: tabletRoot
109 objectName: "tabletRoot"
1110 property string username: "Unknown user"
12− property var eventBridge;
1311 property var rootMenu;
1412 property var openModal: null;
1513 property var openMessage: null;
@@ -112,7 +110,6 @@ Item { · 96 unmodified lines
112110 function openBrowserWindow(request, profile) {
113111 var component = Qt.createComponent("../../controls/TabletWebView.qml");
114112 var newWindow = component.createObject(tabletRoot);
115− newWindow.eventBridge = tabletRoot.eventBridge;
116113 newWindow.remove = true;
117114 newWindow.profile = profile;
118115 request.openIn(newWindow.webView);
@@ -166,21 +163,21 @@ Item { · 47 unmodified lines
166163 objectName: "loader"
167164 asynchronous: false
168165
169−
170166 width: parent.width
171167 height: parent.height
172168
173− onLoaded: {
174− if (loader.item.hasOwnProperty("eventBridge")) {
175− loader.item.eventBridge = eventBridge;
176−
177− // Hook up callback for clara.io download from the marketplace.
178− eventBridge.webEventReceived.connect(function (event) {
179− if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
180− ApplicationInterface.addAssetToWorldFromURL(event.slice(18));
181− }
182− });
169+ // Hook up callback for clara.io download from the marketplace.
170+ Connections {
171+ id: eventBridgeConnection
172+ target: eventBridge
173+ onWebEventReceived: {
174+ if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
175+ ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
176+ }
183177 }
178+ }
179+
180+ onLoaded: {
184181 if (loader.item.hasOwnProperty("sendToScript")) {
185182 loader.item.sendToScript.connect(tabletRoot.sendToScript);
186183 }
interface/resources/qml/hifi/tablet/WindowRoot.qml+11−11
@@ -18,7 +18,6 @@ Windows.ScrollingWindow { · 17 unmodified lines
1818 id: tabletRoot
1919 objectName: "tabletRoot"
2020 property string username: "Unknown user"
21− property var eventBridge;
2221
2322 property var rootMenu;
2423 property string subMenu: ""
@@ -86,17 +85,18 @@ Windows.ScrollingWindow { · 61 unmodified lines
8685 anchors.left: parent.left
8786 anchors.top: parent.top
8887
89− onLoaded: {
90− if (loader.item.hasOwnProperty("eventBridge")) {
91− loader.item.eventBridge = eventBridge;
92−
93− // Hook up callback for clara.io download from the marketplace.
94− eventBridge.webEventReceived.connect(function (event) {
95− if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
96− ApplicationInterface.addAssetToWorldFromURL(event.slice(18));
97− }
98− });
88+ // Hook up callback for clara.io download from the marketplace.
89+ Connections {
90+ id: eventBridgeConnection
91+ target: eventBridge
92+ onWebEventReceived: {
93+ if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
94+ ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
95+ }
9996 }
97+ }
98+
99+ onLoaded: {
100100 if (loader.item.hasOwnProperty("sendToScript")) {
101101 loader.item.sendToScript.connect(tabletRoot.sendToScript);
102102 }
interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml+1−1
@@ -136,7 +136,7 @@ Item { · 135 unmodified lines
136136 for (var i = 0; i < sections.length; i++) {
137137 totalHeight += sections[i].height + sections[i].getPreferencesHeight();
138138 }
139− var bottomPadding = 100;
139+ var bottomPadding = 170;
140140 return (totalHeight + bottomPadding);
141141 }
142142 }
interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml+5−10
@@ -27,8 +27,6 @@ Item { · 26 unmodified lines
2727 property bool keyboardRaised: false
2828 property bool punctuationMode: false
2929
30− property alias eventBridge: eventBridgeWrapper.eventBridge
31−
3230 anchors.fill: parent
3331
3432 BaseWebView {
@@ -43,14 +41,6 @@ Item { · 8 unmodified lines
4341 bottom: footer.top
4442 }
4543
46− QtObject {
47− id: eventBridgeWrapper
48− WebChannel.id: "eventBridgeWrapper"
49− property var eventBridge;
50− }
51−
52− webChannel.registeredObjects: [eventBridgeWrapper]
53−
5444 // Create a global EventBridge object for raiseAndLowerKeyboard.
5545 WebEngineScript {
5646 id: createGlobalEventBridge
@@ -68,6 +58,11 @@ Item { · 11 unmodified lines
6858 }
6959
7060 userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
61+
62+ Component.onCompleted: {
63+ webChannel.registerObject("eventBridge", eventBridge);
64+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
65+ }
7166 }
7267
7368 Rectangle {
interface/resources/snapshot/img/no-image.jpg+0−0

Binary file not shown.

interface/src/Application.cpp+283−241
@@ -11,6 +11,9 @@ · 10 unmodified lines
1111
1212 #include "Application.h"
1313
14+#include <chrono>
15+#include <thread>
16+
1417 #include <gl/Config.h>
1518 #include <glm/glm.hpp>
1619 #include <glm/gtx/component_wise.hpp>
@@ -69,6 +72,7 @@ · 52 unmodified lines
6972 #include <ErrorDialog.h>
7073 #include <FileScriptingInterface.h>
7174 #include <Finally.h>
75+#include <FingerprintUtils.h>
7276 #include <FramebufferCache.h>
7377 #include <gpu/Batch.h>
7478 #include <gpu/Context.h>
@@ -110,7 +114,9 @@ · 35 unmodified lines
110114 #include <render/RenderFetchCullSortTask.h>
111115 #include <RenderDeferredTask.h>
112116 #include <RenderForwardTask.h>
117+#include <RenderViewTask.h>
113118 #include <ResourceCache.h>
119+#include <ResourceRequest.h>
114120 #include <SandboxUtils.h>
115121 #include <SceneScriptingInterface.h>
116122 #include <ScriptEngines.h>
@@ -144,10 +150,8 @@ · 27 unmodified lines
144150 #include "InterfaceLogging.h"
145151 #include "LODManager.h"
146152 #include "ModelPackager.h"
147−#include "networking/HFWebEngineProfile.h"
148−#include "networking/HFTabletWebEngineProfile.h"
149−#include "networking/FileTypeProfile.h"
150153 #include "scripting/Audio.h"
154+#include "networking/CloseEventSender.h"
151155 #include "scripting/TestScriptingInterface.h"
152156 #include "scripting/AccountScriptingInterface.h"
153157 #include "scripting/AssetMappingsScriptingInterface.h"
@@ -244,6 +248,8 @@ static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStanda · 90 unmodified lines
244248 Setting::Handle<int> maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS);
245249
246250 static const QString MARKETPLACE_CDN_HOSTNAME = "mpassets.highfidelity.com";
251+static const int INTERVAL_TO_CHECK_HMD_WORN_STATUS = 500; // milliseconds
252+static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop";
247253
248254 static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
249255
@@ -438,6 +444,10 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 188 unmodified lines
438444 const char* portStr = getCmdOption(argc, constArgv, "--listenPort");
439445 const int listenPort = portStr ? atoi(portStr) : INVALID_PORT;
440446
447+ static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
448+ bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
449+ bool previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt);
450+
441451 Setting::init();
442452
443453 if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
@@ -458,10 +468,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 14 unmodified lines
458468 QCoreApplication::addLibraryPath(audioDLLPath);
459469 #endif
460470
461− static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
462− bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
463− bool previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt);
464−
465471 DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
466472 DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>();
467473 DependencyManager::registerInheritance<EntityDynamicFactoryInterface, InterfaceDynamicFactory>();
@@ -536,6 +542,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 68 unmodified lines
536542 DependencyManager::set<AvatarBookmarks>();
537543 DependencyManager::set<LocationBookmarks>();
538544 DependencyManager::set<Snapshot>();
545+ DependencyManager::set<CloseEventSender>();
539546
540547 return previousSessionCrashed;
541548 }
@@ -649,6 +656,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 107 unmodified lines
649656 Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
650657
651658 auto nodeList = DependencyManager::get<NodeList>();
659+ nodeList->startThread();
652660
653661 // Set up a watchdog thread to intentionally crash the application on deadlocks
654662 _deadlockWatchdogThread = new DeadlockWatchdogThread();
@@ -674,25 +682,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 19 unmodified lines
674682
675683 updateHeartbeat();
676684
677− // start the nodeThread so its event loop is running
678− QThread* nodeThread = new QThread(this);
679− nodeThread->setObjectName("NodeList Thread");
680− nodeThread->start();
681−
682− // make sure the node thread is given highest priority
683− nodeThread->setPriority(QThread::TimeCriticalPriority);
684−
685685 // setup a timer for domain-server check ins
686686 QTimer* domainCheckInTimer = new QTimer(nodeList.data());
687687 connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
688688 domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
689689
690− // put the NodeList and datagram processing on the node thread
691− nodeList->moveToThread(nodeThread);
692−
693− // put the audio processing on a separate thread
694− QThread* audioThread = new QThread();
695− audioThread->setObjectName("Audio Thread");
696690
697691 auto audioIO = DependencyManager::get<AudioClient>();
698692 audioIO->setPositionGetter([]{
@@ -708,7 +702,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 9 unmodified lines
708702 return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY;
709703 });
710704
711− audioIO->moveToThread(audioThread);
712705 recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) {
713706 audioIO->handleRecordedAudioInput(frame->data);
714707 });
@@ -720,11 +713,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 5 unmodified lines
720713 recorder->recordFrame(AUDIO_FRAME_TYPE, audio);
721714 }
722715 });
723−
724− connect(audioThread, &QThread::started, audioIO.data(), &AudioClient::start);
725− connect(audioIO.data(), &AudioClient::destroyed, audioThread, &QThread::quit);
726− connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
727− audioThread->start();
716+ audioIO->startThread();
728717
729718 auto audioScriptingInterface = DependencyManager::set<AudioScriptingInterface, scripting::Audio>();
730719 connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer);
@@ -750,12 +739,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 19 unmodified lines
750739 updateHeartbeat();
751740
752741 // Setup MessagesClient
753− auto messagesClient = DependencyManager::get<MessagesClient>();
754− QThread* messagesThread = new QThread;
755− messagesThread->setObjectName("Messages Client Thread");
756− messagesClient->moveToThread(messagesThread);
757− connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
758− messagesThread->start();
742+ DependencyManager::get<MessagesClient>()->startThread();
759743
760744 const DomainHandler& domainHandler = nodeList->getDomainHandler();
761745
@@ -950,6 +934,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 188 unmodified lines
950934 properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3;
951935 }
952936
937+ // add firstRun flag from settings to launch event
938+ Setting::Handle<bool> firstRun { Settings::firstRun, true };
939+ properties["first_run"] = firstRun.get();
940+
941+ // add the user's machine ID to the launch event
942+ properties["machine_fingerprint"] = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
943+
953944 UserActivityLogger::getInstance().logAction("launch", properties);
954945
955946 // Tell our entity edit sender about our known jurisdictions
@@ -1309,6 +1300,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 353 unmodified lines
13091300 properties["kbps_in"] = bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond();
13101301 properties["kbps_out"] = bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond();
13111302
1303+ properties["atp_in_kbps"] = bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AssetServer);
1304+
13121305 auto nodeList = DependencyManager::get<NodeList>();
13131306 SharedNodePointer entityServerNode = nodeList->soloNodeOfType(NodeType::EntityServer);
13141307 SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
@@ -1322,8 +1315,61 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 7 unmodified lines
13221315 properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1;
13231316
13241317 auto loadingRequests = ResourceCache::getLoadingRequests();
1318+
1319+ QJsonArray loadingRequestsStats;
1320+ for (const auto& request : loadingRequests) {
1321+ QJsonObject requestStats;
1322+ requestStats["filename"] = request->getURL().fileName();
1323+ requestStats["received"] = request->getBytesReceived();
1324+ requestStats["total"] = request->getBytesTotal();
1325+ requestStats["attempts"] = (int)request->getDownloadAttempts();
1326+ loadingRequestsStats.append(requestStats);
1327+ }
1328+
13251329 properties["active_downloads"] = loadingRequests.size();
13261330 properties["pending_downloads"] = ResourceCache::getPendingRequestCount();
1331+ properties["active_downloads_details"] = loadingRequestsStats;
1332+
1333+ auto statTracker = DependencyManager::get<StatTracker>();
1334+
1335+ properties["processing_resources"] = statTracker->getStat("Processing").toInt();
1336+ properties["pending_processing_resources"] = statTracker->getStat("PendingProcessing").toInt();
1337+
1338+ QJsonObject startedRequests;
1339+ startedRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_STARTED).toInt();
1340+ startedRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_STARTED).toInt();
1341+ startedRequests["file"] = statTracker->getStat(STAT_FILE_REQUEST_STARTED).toInt();
1342+ startedRequests["total"] = startedRequests["atp"].toInt() + startedRequests["http"].toInt()
1343+ + startedRequests["file"].toInt();
1344+ properties["started_requests"] = startedRequests;
1345+
1346+ QJsonObject successfulRequests;
1347+ successfulRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_SUCCESS).toInt();
1348+ successfulRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_SUCCESS).toInt();
1349+ successfulRequests["file"] = statTracker->getStat(STAT_FILE_REQUEST_SUCCESS).toInt();
1350+ successfulRequests["total"] = successfulRequests["atp"].toInt() + successfulRequests["http"].toInt()
1351+ + successfulRequests["file"].toInt();
1352+ properties["successful_requests"] = successfulRequests;
1353+
1354+ QJsonObject failedRequests;
1355+ failedRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_FAILED).toInt();
1356+ failedRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_FAILED).toInt();
1357+ failedRequests["file"] = statTracker->getStat(STAT_FILE_REQUEST_FAILED).toInt();
1358+ failedRequests["total"] = failedRequests["atp"].toInt() + failedRequests["http"].toInt()
1359+ + failedRequests["file"].toInt();
1360+ properties["failed_requests"] = failedRequests;
1361+
1362+ QJsonObject cacheRequests;
1363+ cacheRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_CACHE).toInt();
1364+ cacheRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_CACHE).toInt();
1365+ cacheRequests["total"] = cacheRequests["atp"].toInt() + cacheRequests["http"].toInt();
1366+ properties["cache_requests"] = cacheRequests;
1367+
1368+ QJsonObject atpMappingRequests;
1369+ atpMappingRequests["started"] = statTracker->getStat(STAT_ATP_MAPPING_REQUEST_STARTED).toInt();
1370+ atpMappingRequests["failed"] = statTracker->getStat(STAT_ATP_MAPPING_REQUEST_FAILED).toInt();
1371+ atpMappingRequests["successful"] = statTracker->getStat(STAT_ATP_MAPPING_REQUEST_SUCCESS).toInt();
1372+ properties["atp_mapping_requests"] = atpMappingRequests;
13271373
13281374 properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false;
13291375
@@ -1339,9 +1385,43 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 9 unmodified lines
13391385 properties["deleted_entity_cnt"] = entityActivityTracking.deletedEntityCount;
13401386 properties["edited_entity_cnt"] = entityActivityTracking.editedEntityCount;
13411387
1388+ NodeToOctreeSceneStats* octreeServerSceneStats = getOcteeSceneStats();
1389+ unsigned long totalServerOctreeElements = 0;
1390+ for (NodeToOctreeSceneStatsIterator i = octreeServerSceneStats->begin(); i != octreeServerSceneStats->end(); i++) {
1391+ totalServerOctreeElements += i->second.getTotalElements();
1392+ }
1393+
1394+ properties["local_octree_elements"] = (qint64) OctreeElement::getInternalNodeCount();
1395+ properties["server_octree_elements"] = (qint64) totalServerOctreeElements;
1396+
13421397 properties["active_display_plugin"] = getActiveDisplayPlugin()->getName();
13431398 properties["using_hmd"] = isHMDMode();
13441399
1400+ _autoSwitchDisplayModeSupportedHMDPlugin = nullptr;
1401+ foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
1402+ if (displayPlugin->isHmd() &&
1403+ displayPlugin->getSupportsAutoSwitch()) {
1404+ _autoSwitchDisplayModeSupportedHMDPlugin = displayPlugin;
1405+ _autoSwitchDisplayModeSupportedHMDPluginName =
1406+ _autoSwitchDisplayModeSupportedHMDPlugin->getName();
1407+ _previousHMDWornStatus =
1408+ _autoSwitchDisplayModeSupportedHMDPlugin->isDisplayVisible();
1409+ break;
1410+ }
1411+ }
1412+
1413+ if (_autoSwitchDisplayModeSupportedHMDPlugin) {
1414+ if (getActiveDisplayPlugin() != _autoSwitchDisplayModeSupportedHMDPlugin &&
1415+ !_autoSwitchDisplayModeSupportedHMDPlugin->isSessionActive()) {
1416+ startHMDStandBySession();
1417+ }
1418+ // Poll periodically to check whether the user has worn HMD or not. Switch Display mode accordingly.
1419+ // If the user wears HMD then switch to VR mode. If the user removes HMD then switch to Desktop mode.
1420+ QTimer* autoSwitchDisplayModeTimer = new QTimer(this);
1421+ connect(autoSwitchDisplayModeTimer, SIGNAL(timeout()), this, SLOT(switchDisplayMode()));
1422+ autoSwitchDisplayModeTimer->start(INTERVAL_TO_CHECK_HMD_WORN_STATUS);
1423+ }
1424+
13451425 auto glInfo = getGLContextData();
13461426 properties["gl_info"] = glInfo;
13471427 properties["gpu_used_memory"] = (int)BYTES_TO_MB(gpu::Context::getUsedGPUMemSize());
@@ -1364,6 +1444,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 16 unmodified lines
13641444 lastLeftHandPose = leftHandPose;
13651445 lastRightHandPose = rightHandPose;
13661446
1447+ properties["local_socket_changes"] = DependencyManager::get<StatTracker>()->getStat(LOCAL_SOCKET_CHANGE_STAT).toInt();
1448+
13671449 UserActivityLogger::getInstance().logAction("stats", properties);
13681450 });
13691451 sendStatsTimer->start();
@@ -1463,6 +1545,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 93 unmodified lines
14631545 updateSystemTabletMode();
14641546
14651547 connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged);
1548+
1549+ qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
14661550 }
14671551
14681552 void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
@@ -1571,6 +1655,12 @@ void Application::aboutToQuit() { · 102 unmodified lines
15711655 }
15721656
15731657 getActiveDisplayPlugin()->deactivate();
1658+ if (_autoSwitchDisplayModeSupportedHMDPlugin
1659+ && _autoSwitchDisplayModeSupportedHMDPlugin->isSessionActive()) {
1660+ _autoSwitchDisplayModeSupportedHMDPlugin->endSession();
1661+ }
1662+ // use the CloseEventSender via a QThread to send an event that says the user asked for the app to close
1663+ DependencyManager::get<CloseEventSender>()->startThread();
15741664
15751665 // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown.
15761666 DependencyManager::get<OffscreenUi>()->hide("RunningScripts");
@@ -1658,6 +1748,8 @@ void Application::cleanupBeforeQuit() { · 81 unmodified lines
16581748 // stop QML
16591749 DependencyManager::destroy<OffscreenUi>();
16601750
1751+ DependencyManager::destroy<OffscreenQmlSurfaceCache>();
1752+
16611753 if (_snapshotSoundInjector != nullptr) {
16621754 _snapshotSoundInjector->stop();
16631755 }
@@ -1685,6 +1777,10 @@ Application::~Application() { · 21 unmodified lines
16851777
16861778 _physicsEngine->setCharacterController(nullptr);
16871779
1780+ // the _shapeManager should have zero references
1781+ _shapeManager.collectGarbage();
1782+ assert(_shapeManager.getNumShapes() == 0);
1783+
16881784 // shutdown render engine
16891785 _main3DScene = nullptr;
16901786 _renderEngine = nullptr;
@@ -1713,15 +1809,9 @@ Application::~Application() { · 22 unmodified lines
17131809
17141810 ResourceManager::cleanup();
17151811
1716− QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
1717−
17181812 // remove the NodeList from the DependencyManager
17191813 DependencyManager::destroy<NodeList>();
17201814
1721− // ask the node thread to quit and wait until it is done
1722− nodeThread->quit();
1723− nodeThread->wait();
1724−
17251815 Leapmotion::destroy();
17261816
17271817 if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
@@ -1736,6 +1826,15 @@ Application::~Application() { · 8 unmodified lines
17361826
17371827 _window->deleteLater();
17381828
1829+ // make sure that the quit event has finished sending before we take the application down
1830+ auto closeEventSender = DependencyManager::get<CloseEventSender>();
1831+ while (!closeEventSender->hasFinishedQuitEvent() && !closeEventSender->hasTimedOutQuitEvent()) {
1832+ // sleep a little so we're not spinning at 100%
1833+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
1834+ }
1835+ // quit the thread used by the closure event sender
1836+ closeEventSender->thread()->quit();
1837+
17391838 // Can't log to file passed this point, FileLogger about to be deleted
17401839 qInstallMessageHandler(LogHandler::verboseMessageHandler);
17411840 }
@@ -1771,15 +1870,9 @@ void Application::initializeGL() { · 29 unmodified lines
17711870
17721871 // Set up the render engine
17731872 render::CullFunctor cullFunctor = LODManager::shouldRender;
1774− _renderEngine->addJob<RenderShadowTask>("RenderShadowTask", cullFunctor);
1775− const auto items = _renderEngine->addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
1776− assert(items.canCast<RenderFetchCullSortTask::Output>());
17771873 static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
1778− if (QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD)) {
1779− _renderEngine->addJob<RenderForwardTask>("Forward", items);
1780− } else {
1781− _renderEngine->addJob<RenderDeferredTask>("RenderDeferredTask", items);
1782− }
1874+ bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
1875+ _renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, isDeferred);
17831876 _renderEngine->load();
17841877 _renderEngine->registerScene(_main3DScene);
17851878
@@ -1828,14 +1921,10 @@ void Application::initializeUi() { · 42 unmodified lines
18281921 UpdateDialog::registerType();
18291922 qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
18301923
1831− qmlRegisterType<HFWebEngineProfile>("HFWebEngineProfile", 1, 0, "HFWebEngineProfile");
1832− qmlRegisterType<HFTabletWebEngineProfile>("HFTabletWebEngineProfile", 1, 0, "HFTabletWebEngineProfile");
1833− qmlRegisterType<FileTypeProfile>("FileTypeProfile", 1, 0, "FileTypeProfile");
1834−
18351924 auto offscreenUi = DependencyManager::get<OffscreenUi>();
18361925 offscreenUi->create(_glWidget->qglContext());
18371926
1838− auto rootContext = offscreenUi->getRootContext();
1927+ auto surfaceContext = offscreenUi->getSurfaceContext();
18391928
18401929 offscreenUi->setProxyWindow(_window->windowHandle());
18411930 offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
@@ -1847,7 +1936,7 @@ void Application::initializeUi() { · 5 unmodified lines
18471936 // do better detection in the offscreen UI of what has focus
18481937 offscreenUi->setNavigationFocused(false);
18491938
1850− auto engine = rootContext->engine();
1939+ auto engine = surfaceContext->engine();
18511940 connect(engine, &QQmlEngine::quit, [] {
18521941 qApp->quit();
18531942 });
@@ -1856,78 +1945,77 @@ void Application::initializeUi() { · 2 unmodified lines
18561945
18571946 // For some reason there is already an "Application" object in the QML context,
18581947 // though I can't find it. Hence, "ApplicationInterface"
1859− rootContext->setContextProperty("ApplicationInterface", this);
1860− rootContext->setContextProperty("Audio", DependencyManager::get<AudioScriptingInterface>().data());
1861− rootContext->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
1862− rootContext->setContextProperty("AudioScope", DependencyManager::get<AudioScope>().data());
1948+ surfaceContext->setContextProperty("Audio", DependencyManager::get<AudioScriptingInterface>().data());
1949+ surfaceContext->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
1950+ surfaceContext->setContextProperty("AudioScope", DependencyManager::get<AudioScope>().data());
18631951
1864− rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
1865− rootContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
1952+ surfaceContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
1953+ surfaceContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
18661954 _fileDownload = new FileScriptingInterface(engine);
1867− rootContext->setContextProperty("File", _fileDownload);
1955+ surfaceContext->setContextProperty("File", _fileDownload);
18681956 connect(_fileDownload, &FileScriptingInterface::unzipResult, this, &Application::handleUnzip);
1869− rootContext->setContextProperty("MyAvatar", getMyAvatar().get());
1870− rootContext->setContextProperty("Messages", DependencyManager::get<MessagesClient>().data());
1871− rootContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
1872− rootContext->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
1873− rootContext->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
1874− rootContext->setContextProperty("FrameTimings", &_frameTimingsScriptingInterface);
1875− rootContext->setContextProperty("Rates", new RatesScriptingInterface(this));
1876− rootContext->setContextProperty("pathToFonts", "../../");
1957+ surfaceContext->setContextProperty("MyAvatar", getMyAvatar().get());
1958+ surfaceContext->setContextProperty("Messages", DependencyManager::get<MessagesClient>().data());
1959+ surfaceContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
1960+ surfaceContext->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
1961+ surfaceContext->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
1962+ surfaceContext->setContextProperty("FrameTimings", &_frameTimingsScriptingInterface);
1963+ surfaceContext->setContextProperty("Rates", new RatesScriptingInterface(this));
18771964
1878− rootContext->setContextProperty("TREE_SCALE", TREE_SCALE);
1879− rootContext->setContextProperty("Quat", new Quat());
1880− rootContext->setContextProperty("Vec3", new Vec3());
1881− rootContext->setContextProperty("Uuid", new ScriptUUID());
1882− rootContext->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
1965+ surfaceContext->setContextProperty("TREE_SCALE", TREE_SCALE);
1966+ // FIXME Quat and Vec3 won't work with QJSEngine used by QML
1967+ surfaceContext->setContextProperty("Quat", new Quat());
1968+ surfaceContext->setContextProperty("Vec3", new Vec3());
1969+ surfaceContext->setContextProperty("Uuid", new ScriptUUID());
1970+ surfaceContext->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
18831971
1884− rootContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
1885− rootContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
1972+ surfaceContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
1973+ surfaceContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
18861974
1887− rootContext->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
1975+ surfaceContext->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
18881976
1889− rootContext->setContextProperty("Camera", &_myCamera);
1977+ surfaceContext->setContextProperty("Camera", &_myCamera);
18901978
18911979 #if defined(Q_OS_MAC) || defined(Q_OS_WIN)
1892− rootContext->setContextProperty("SpeechRecognizer", DependencyManager::get<SpeechRecognizer>().data());
1980+ surfaceContext->setContextProperty("SpeechRecognizer", DependencyManager::get<SpeechRecognizer>().data());
18931981 #endif
18941982
1895− rootContext->setContextProperty("Overlays", &_overlays);
1896− rootContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
1897− rootContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
1898− rootContext->setContextProperty("Stats", Stats::getInstance());
1899− rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
1900− rootContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
1901− rootContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
1902− rootContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
1983+ surfaceContext->setContextProperty("Overlays", &_overlays);
1984+ surfaceContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
1985+ surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
1986+ surfaceContext->setContextProperty("Stats", Stats::getInstance());
1987+ surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
1988+ surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
1989+ surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
1990+ surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
19031991
19041992 // Caches
1905− rootContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data());
1906− rootContext->setContextProperty("TextureCache", DependencyManager::get<TextureCache>().data());
1907− rootContext->setContextProperty("ModelCache", DependencyManager::get<ModelCache>().data());
1908− rootContext->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
1909−
1910− rootContext->setContextProperty("Account", AccountScriptingInterface::getInstance());
1911− rootContext->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
1912− rootContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface);
1913− rootContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance());
1914− rootContext->setContextProperty("FaceTracker", DependencyManager::get<DdeFaceTracker>().data());
1915− rootContext->setContextProperty("AvatarManager", DependencyManager::get<AvatarManager>().data());
1916− rootContext->setContextProperty("UndoStack", &_undoStackScriptingInterface);
1917− rootContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
1918− rootContext->setContextProperty("Paths", DependencyManager::get<PathUtils>().data());
1919− rootContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
1920− rootContext->setContextProperty("Scene", DependencyManager::get<SceneScriptingInterface>().data());
1921− rootContext->setContextProperty("Render", _renderEngine->getConfiguration().get());
1922− rootContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface());
1923− rootContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
1924−
1925− rootContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
1926−
1927− rootContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
1993+ surfaceContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data());
1994+ surfaceContext->setContextProperty("TextureCache", DependencyManager::get<TextureCache>().data());
1995+ surfaceContext->setContextProperty("ModelCache", DependencyManager::get<ModelCache>().data());
1996+ surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
1997+
1998+ surfaceContext->setContextProperty("Account", AccountScriptingInterface::getInstance());
1999+ surfaceContext->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
2000+ surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface);
2001+ surfaceContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance());
2002+ surfaceContext->setContextProperty("FaceTracker", DependencyManager::get<DdeFaceTracker>().data());
2003+ surfaceContext->setContextProperty("AvatarManager", DependencyManager::get<AvatarManager>().data());
2004+ surfaceContext->setContextProperty("UndoStack", &_undoStackScriptingInterface);
2005+ surfaceContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
2006+ surfaceContext->setContextProperty("Paths", DependencyManager::get<PathUtils>().data());
2007+ surfaceContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
2008+ surfaceContext->setContextProperty("Scene", DependencyManager::get<SceneScriptingInterface>().data());
2009+ surfaceContext->setContextProperty("Render", _renderEngine->getConfiguration().get());
2010+ surfaceContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface());
2011+ surfaceContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
2012+
2013+ surfaceContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
2014+
2015+ surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
19282016
19292017 if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
1930− rootContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get()));
2018+ surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get()));
19312019 }
19322020
19332021
@@ -2066,7 +2154,7 @@ void Application::paintGL() { · 132 unmodified lines
20662154 _myCamera.setOrientation(glm::quat_cast(camMat));
20672155 } else {
20682156 _myCamera.setPosition(myAvatar->getDefaultEyePosition());
2069− _myCamera.setOrientation(myAvatar->getMyHead()->getCameraOrientation());
2157+ _myCamera.setOrientation(myAvatar->getMyHead()->getHeadOrientation());
20702158 }
20712159 } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
20722160 if (isHMDMode()) {
@@ -2186,6 +2274,9 @@ void Application::paintGL() { · 113 unmodified lines
21862274 });
21872275 renderArgs._context->setStereoProjections(eyeProjections);
21882276 renderArgs._context->setStereoViews(eyeOffsets);
2277+
2278+ // Configure the type of display / stereo
2279+ renderArgs._displayMode = (isHMDMode() ? RenderArgs::STEREO_HMD : RenderArgs::STEREO_MONITOR);
21892280 }
21902281 renderArgs._blitFramebuffer = finalFramebuffer;
21912282 displaySide(&renderArgs, _myCamera);
@@ -2379,15 +2470,16 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { · 187 unmodified lines
23792470
23802471 // Check HMD use (may be technically available without being in use)
23812472 bool hasHMD = PluginUtils::isHMDAvailable();
2382− bool isUsingHMD = hasHMD && hasHandControllers && _displayPlugin->isHmd();
2473+ bool isUsingHMD = _displayPlugin->isHmd();
2474+ bool isUsingHMDAndHandControllers = hasHMD && hasHandControllers && isUsingHMD;
23832475
23842476 Setting::Handle<bool> tutorialComplete{ "tutorialComplete", false };
23852477 Setting::Handle<bool> firstRun{ Settings::firstRun, true };
23862478
23872479 bool isTutorialComplete = tutorialComplete.get();
2388− bool shouldGoToTutorial = isUsingHMD && hasTutorialContent && !isTutorialComplete;
2480+ bool shouldGoToTutorial = isUsingHMDAndHandControllers && hasTutorialContent && !isTutorialComplete;
23892481
2390− qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMD;
2482+ qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMDAndHandControllers;
23912483 qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent <<
23922484 ", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial;
23932485
@@ -2401,10 +2493,18 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { · 7 unmodified lines
24012493
24022494 const QString TUTORIAL_PATH = "/tutorial_begin";
24032495
2496+ static const QString SENT_TO_TUTORIAL = "tutorial";
2497+ static const QString SENT_TO_PREVIOUS_LOCATION = "previous_location";
2498+ static const QString SENT_TO_ENTRY = "entry";
2499+ static const QString SENT_TO_SANDBOX = "sandbox";
2500+
2501+ QString sentTo;
2502+
24042503 if (shouldGoToTutorial) {
24052504 if (sandboxIsRunning) {
24062505 qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
24072506 DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
2507+ sentTo = SENT_TO_TUTORIAL;
24082508 } else {
24092509 qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
24102510 if (firstRun.get()) {
@@ -2412,8 +2512,10 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { · 1 unmodified line
24122512 }
24132513 if (addressLookupString.isEmpty()) {
24142514 DependencyManager::get<AddressManager>()->goToEntry();
2515+ sentTo = SENT_TO_ENTRY;
24152516 } else {
24162517 DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
2518+ sentTo = SENT_TO_PREVIOUS_LOCATION;
24172519 }
24182520 }
24192521 } else {
@@ -2426,23 +2528,40 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { · 6 unmodified lines
24262528
24272529 // If this is a first run we short-circuit the address passed in
24282530 if (isFirstRun) {
2429− if (isUsingHMD) {
2531+ if (isUsingHMDAndHandControllers) {
24302532 if (sandboxIsRunning) {
24312533 qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
24322534 DependencyManager::get<AddressManager>()->goToLocalSandbox();
2535+ sentTo = SENT_TO_SANDBOX;
24332536 } else {
24342537 qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
24352538 DependencyManager::get<AddressManager>()->goToEntry();
2539+ sentTo = SENT_TO_ENTRY;
24362540 }
24372541 } else {
24382542 DependencyManager::get<AddressManager>()->goToEntry();
2543+ sentTo = SENT_TO_ENTRY;
24392544 }
24402545 } else {
24412546 qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
24422547 DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
2548+ sentTo = SENT_TO_PREVIOUS_LOCATION;
24432549 }
24442550 }
24452551
2552+ UserActivityLogger::getInstance().logAction("startup_sent_to", {
2553+ { "sent_to", sentTo },
2554+ { "sandbox_is_running", sandboxIsRunning },
2555+ { "has_hmd", hasHMD },
2556+ { "has_hand_controllers", hasHandControllers },
2557+ { "is_using_hmd", isUsingHMD },
2558+ { "is_using_hmd_and_hand_controllers", isUsingHMDAndHandControllers },
2559+ { "content_version", contentVersion },
2560+ { "is_tutorial_complete", isTutorialComplete },
2561+ { "has_tutorial_content", hasTutorialContent },
2562+ { "should_go_to_tutorial", shouldGoToTutorial }
2563+ });
2564+
24462565 _connectionMonitor.init();
24472566
24482567 // After all of the constructor is completed, then set firstRun to false.
@@ -2777,6 +2896,17 @@ void Application::keyPressEvent(QKeyEvent* event) { · 328 unmodified lines
27772896 if (isShifted && isMeta && !isOption) {
27782897 Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings);
27792898 } else if (!isOption && !isShifted && isMeta) {
2899+ AudioInjectorOptions options;
2900+ options.localOnly = true;
2901+ options.stereo = true;
2902+
2903+ if (_snapshotSoundInjector) {
2904+ _snapshotSoundInjector->setOptions(options);
2905+ _snapshotSoundInjector->restart();
2906+ } else {
2907+ QByteArray samples = _snapshotSound->getByteArray();
2908+ _snapshotSoundInjector = AudioInjector::playSound(samples, options);
2909+ }
27802910 takeSnapshot(true);
27812911 }
27822912 break;
@@ -3951,6 +4081,7 @@ void Application::updateMyAvatarLookAtPosition() { · 1168 unmodified lines
39514081 lookAtPosition.x = -lookAtPosition.x;
39524082 }
39534083 if (isHMD) {
4084+ // TODO -- this code is probably wrong, getHeadPose() returns something in sensor frame, not avatar
39544085 glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose();
39554086 glm::quat hmdRotation = glm::quat_cast(headPose);
39564087 lookAtSpot = _myCamera.getPosition() + myAvatar->getOrientation() * (hmdRotation * lookAtPosition);
@@ -3992,9 +4123,10 @@ void Application::updateMyAvatarLookAtPosition() { · 35 unmodified lines
39924123 }
39934124 } else {
39944125 // I am not looking at anyone else, so just look forward
3995− if (isHMD) {
3996− glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
3997− lookAtSpot = transformPoint(worldHMDMat, glm::vec3(0.0f, 0.0f, -TREE_SCALE));
4126+ auto headPose = myAvatar->getHeadControllerPoseInSensorFrame();
4127+ if (headPose.isValid()) {
4128+ glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() * headPose.getMatrix();
4129+ lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, TREE_SCALE));
39984130 } else {
39994131 lookAtSpot = myAvatar->getHead()->getEyePosition() +
40004132 (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE));
@@ -4492,12 +4624,13 @@ void Application::update(float deltaTime) { · 491 unmodified lines
44924624
44934625 getEntities()->getTree()->withWriteLock([&] {
44944626 PerformanceTimer perfTimer("handleOutgoingChanges");
4495− const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
4496− _entitySimulation->handleDeactivatedMotionStates(deactivations);
44974627
44984628 const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates();
44994629 _entitySimulation->handleChangedMotionStates(outgoingChanges);
45004630 avatarManager->handleChangedMotionStates(outgoingChanges);
4631+
4632+ const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
4633+ _entitySimulation->handleDeactivatedMotionStates(deactivations);
45014634 });
45024635
45034636 if (!_aboutToQuit) {
@@ -4885,12 +5018,6 @@ QRect Application::getDesirableApplicationGeometry() const { · 381 unmodified lines
48855018 return applicationGeometry;
48865019 }
48875020
4888−glm::vec3 Application::getSunDirection() const {
4889− // Sun direction is in fact just the location of the sun relative to the origin
4890− auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
4891− return skyStage->getSunLight()->getDirection();
4892−}
4893−
48945021 // FIXME, preprocessor guard this check to occur only in DEBUG builds
48955022 static QThread * activeRenderingThread = nullptr;
48965023
@@ -4947,7 +5074,7 @@ namespace render { · 50 unmodified lines
49475074 template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); }
49485075 template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); }
49495076 template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) {
4950− if (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) {
5077+ if (Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) {
49515078 PerformanceTimer perfTimer("worldBox");
49525079
49535080 auto& batch = *args->_batch;
@@ -4957,90 +5084,6 @@ namespace render { · 3 unmodified lines
49575084 }
49585085 }
49595086
4960−// Background Render Data & rendering functions
4961−class BackgroundRenderData {
4962−public:
4963− typedef render::Payload<BackgroundRenderData> Payload;
4964− typedef Payload::DataPointer Pointer;
4965−
4966− static render::ItemID _item; // unique WorldBoxRenderData
4967−};
4968−
4969−render::ItemID BackgroundRenderData::_item = 0;
4970−
4971−namespace render {
4972− template <> const ItemKey payloadGetKey(const BackgroundRenderData::Pointer& stuff) {
4973− return ItemKey::Builder::background();
4974− }
4975−
4976− template <> const Item::Bound payloadGetBound(const BackgroundRenderData::Pointer& stuff) {
4977− return Item::Bound();
4978− }
4979−
4980− template <> void payloadRender(const BackgroundRenderData::Pointer& background, RenderArgs* args) {
4981− Q_ASSERT(args->_batch);
4982− gpu::Batch& batch = *args->_batch;
4983−
4984− // Background rendering decision
4985− auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
4986− auto backgroundMode = skyStage->getBackgroundMode();
4987−
4988− switch (backgroundMode) {
4989− case model::SunSkyStage::SKY_DEFAULT: {
4990− auto scene = DependencyManager::get<SceneScriptingInterface>()->getStage();
4991− auto sceneKeyLight = scene->getKeyLight();
4992−
4993− scene->setSunModelEnable(false);
4994− sceneKeyLight->setColor(ColorUtils::toVec3(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_COLOR));
4995− sceneKeyLight->setIntensity(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_INTENSITY);
4996− sceneKeyLight->setAmbientIntensity(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY);
4997− sceneKeyLight->setDirection(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_DIRECTION);
4998− // fall through: render a skybox (if available), or the defaults (if requested)
4999− }
5000−
5001− case model::SunSkyStage::SKY_BOX: {
5002− auto skybox = skyStage->getSkybox();
5003− if (!skybox->empty()) {
5004− PerformanceTimer perfTimer("skybox");
5005− skybox->render(batch, args->getViewFrustum());
5006− break;
5007− }
5008− // fall through: render defaults (if requested)
5009− }
5010−
5011− case model::SunSkyStage::SKY_DEFAULT_AMBIENT_TEXTURE: {
5012− if (Menu::getInstance()->isOptionChecked(MenuOption::DefaultSkybox)) {
5013− auto scene = DependencyManager::get<SceneScriptingInterface>()->getStage();
5014− auto sceneKeyLight = scene->getKeyLight();
5015− auto defaultSkyboxAmbientTexture = qApp->getDefaultSkyboxAmbientTexture();
5016− if (defaultSkyboxAmbientTexture) {
5017− sceneKeyLight->setAmbientSphere(defaultSkyboxAmbientTexture->getIrradiance());
5018− sceneKeyLight->setAmbientMap(defaultSkyboxAmbientTexture);
5019− } else {
5020− static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(
5021− "Failed to get a valid Default Skybox Ambient Texture ? probably because it couldn't be find during initialization step");
5022− }
5023− // fall through: render defaults skybox
5024− } else {
5025− break;
5026− }
5027− }
5028−
5029− case model::SunSkyStage::SKY_DEFAULT_TEXTURE:
5030− if (Menu::getInstance()->isOptionChecked(MenuOption::DefaultSkybox)) {
5031− qApp->getDefaultSkybox()->render(batch, args->getViewFrustum());
5032− }
5033− break;
5034−
5035− // Any other cases require no extra rendering
5036− case model::SunSkyStage::NO_BACKGROUND:
5037− default:
5038− break;
5039− }
5040− }
5041−}
5042−
5043−
50445087 void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool selfAvatarOnly) {
50455088
50465089 // FIXME: This preDisplayRender call is temporary until we create a separate render::scene for the mirror rendering.
@@ -5064,15 +5107,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se · 17 unmodified lines
50645107 // The pending changes collecting the changes here
50655108 render::Transaction transaction;
50665109
5067− // FIXME: Move this out of here!, Background / skybox should be driven by the enityt content just like the other entities
5068− // Background rendering decision
5069− if (!render::Item::isValidID(BackgroundRenderData::_item)) {
5070− auto backgroundRenderData = make_shared<BackgroundRenderData>();
5071− auto backgroundRenderPayload = make_shared<BackgroundRenderData::Payload>(backgroundRenderData);
5072− BackgroundRenderData::_item = _main3DScene->allocateID();
5073− transaction.resetItem(BackgroundRenderData::_item, backgroundRenderPayload);
5074− }
5075−
50765110 // Assuming nothing gets rendered through that
50775111 if (!selfAvatarOnly) {
50785112 if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) {
@@ -5108,12 +5142,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se · 29 unmodified lines
51085142 });
51095143 }
51105144
5111− // Setup the current Zone Entity lighting
5112− {
5113− auto stage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
5114− DependencyManager::get<DeferredLightingEffect>()->setGlobalLight(stage->getSunLight());
5115− }
5116−
51175145 {
51185146 PerformanceTimer perfTimer("SceneProcessTransaction");
51195147 _main3DScene->enqueueTransaction(transaction);
@@ -6384,21 +6412,6 @@ void Application::loadAddAvatarBookmarkDialog() const { · 1264 unmodified lines
63846412 }
63856413
63866414 void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) {
6387−
6388− //keep sound thread out of event loop scope
6389−
6390− AudioInjectorOptions options;
6391− options.localOnly = true;
6392− options.stereo = true;
6393−
6394− if (_snapshotSoundInjector) {
6395− _snapshotSoundInjector->setOptions(options);
6396− _snapshotSoundInjector->restart();
6397− } else {
6398− QByteArray samples = _snapshotSound->getByteArray();
6399− _snapshotSoundInjector = AudioInjector::playSound(samples, options);
6400− }
6401−
64026415 postLambdaEvent([notify, includeAnimated, aspectRatio, this] {
64036416 // Get a screenshot and save it
64046417 QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio));
@@ -6811,6 +6824,35 @@ void Application::updateDisplayMode() { · 406 unmodified lines
68116824 Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
68126825 }
68136826
6827+void Application::switchDisplayMode() {
6828+ if (!_autoSwitchDisplayModeSupportedHMDPlugin) {
6829+ return;
6830+ }
6831+ bool currentHMDWornStatus = _autoSwitchDisplayModeSupportedHMDPlugin->isDisplayVisible();
6832+ if (currentHMDWornStatus != _previousHMDWornStatus) {
6833+ // Switch to respective mode as soon as currentHMDWornStatus changes
6834+ if (currentHMDWornStatus) {
6835+ qCDebug(interfaceapp) << "Switching from Desktop to HMD mode";
6836+ endHMDSession();
6837+ setActiveDisplayPlugin(_autoSwitchDisplayModeSupportedHMDPluginName);
6838+ } else {
6839+ qCDebug(interfaceapp) << "Switching from HMD to desktop mode";
6840+ setActiveDisplayPlugin(DESKTOP_DISPLAY_PLUGIN_NAME);
6841+ startHMDStandBySession();
6842+ }
6843+ emit activeDisplayPluginChanged();
6844+ }
6845+ _previousHMDWornStatus = currentHMDWornStatus;
6846+}
6847+
6848+void Application::startHMDStandBySession() {
6849+ _autoSwitchDisplayModeSupportedHMDPlugin->startStandBySession();
6850+}
6851+
6852+void Application::endHMDSession() {
6853+ _autoSwitchDisplayModeSupportedHMDPlugin->endSession();
6854+}
6855+
68146856 mat4 Application::getEyeProjection(int eye) const {
68156857 QMutexLocker viewLocker(&_viewMutex);
68166858 if (isHMDMode()) {
interface/src/Application.h+7−5
@@ -439,7 +439,7 @@ private slots: · 438 unmodified lines
439439 void addAssetToWorldErrorTimeout();
440440
441441 void handleSandboxStatus(QNetworkReply* reply);
442−
442+ void switchDisplayMode();
443443 private:
444444 static void initDisplay();
445445 void init();
@@ -457,8 +457,6 @@ private: · 11 unmodified lines
457457
458458 void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend = false);
459459
460− glm::vec3 getSunDirection() const;
461−
462460 void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed);
463461
464462 int sendNackPackets();
@@ -679,7 +677,11 @@ private: · 214 unmodified lines
679677 FileScriptingInterface* _fileDownload;
680678 AudioInjector* _snapshotSoundInjector { nullptr };
681679 SharedSoundPointer _snapshotSound;
680+
681+ DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin;
682+ QString _autoSwitchDisplayModeSupportedHMDPluginName;
683+ bool _previousHMDWornStatus;
684+ void startHMDStandBySession();
685+ void endHMDSession();
682686 };
683−
684−
685687 #endif // hifi_Application_h
interface/src/AvatarBookmarks.cpp+22−2
@@ -21,15 +21,35 @@ · 20 unmodified lines
2121
2222 #include "MainWindow.h"
2323 #include "Menu.h"
24−
2524 #include "AvatarBookmarks.h"
25+#include "InterfaceLogging.h"
26+
2627 #include <QtQuick/QQuickWindow>
2728
2829 AvatarBookmarks::AvatarBookmarks() {
29− _bookmarksFilename = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
30+ _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME;
3031 readFromFile();
3132 }
3233
34+void AvatarBookmarks::readFromFile() {
35+ // migrate old avatarbookmarks.json, used to be in 'local' folder on windows
36+ QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
37+ QFile oldConfig(oldConfigPath);
38+
39+ // I imagine that in a year from now, this code for migrating (as well as the two lines above)
40+ // may be removed since all bookmarks should have been migrated by then
41+ // - Robbie Uvanni (6.8.2017)
42+ if (oldConfig.exists()) {
43+ if (QDir().rename(oldConfigPath, _bookmarksFilename)) {
44+ qCDebug(interfaceapp) << "Successfully migrated" << AVATARBOOKMARKS_FILENAME;
45+ } else {
46+ qCDebug(interfaceapp) << "Failed to migrate" << AVATARBOOKMARKS_FILENAME;
47+ }
48+ }
49+
50+ Bookmarks::readFromFile();
51+}
52+
3353 void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
3454 // Add menus/actions
3555 auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar);
interface/src/AvatarBookmarks.h+1−0
@@ -29,6 +29,7 @@ public slots: · 28 unmodified lines
2929
3030 protected:
3131 void addBookmarkToMenu(Menu* menubar, const QString& name, const QString& address) override;
32+ void readFromFile();
3233
3334 private:
3435 const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";
interface/src/CrashHandler.cpp+20−8
@@ -24,12 +24,15 @@ · 23 unmodified lines
2424
2525 #include "Application.h"
2626 #include "Menu.h"
27−#include <SettingHandle.h>
2827
2928 #include <RunningMarker.h>
29+#include <SettingHandle.h>
30+#include <SettingHelpers.h>
31+
3032
3133 bool CrashHandler::checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt) {
32− Settings settings;
34+ QSettings::setDefaultFormat(JSON_FORMAT);
35+ QSettings settings;
3336 settings.beginGroup("Developer");
3437 QVariant displayCrashOptions = settings.value(MenuOption::DisplayCrashOptions);
3538 QVariant askToResetSettingsOption = settings.value(MenuOption::AskToResetSettings);
@@ -70,7 +73,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { · 34 unmodified lines
7073 layout->addWidget(label);
7174
7275 QRadioButton* option1 = new QRadioButton("Reset all my settings");
73− QRadioButton* option2 = new QRadioButton("Reset my settings but retain avatar info.");
76+ QRadioButton* option2 = new QRadioButton("Reset my settings but keep essential info");
7477 QRadioButton* option3 = new QRadioButton("Continue with my current settings");
7578 option3->setChecked(true);
7679 layout->addWidget(option1);
@@ -92,7 +95,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { · 15 unmodified lines
9295 return CrashHandler::DELETE_INTERFACE_INI;
9396 }
9497 if (option2->isChecked()) {
95− return CrashHandler::RETAIN_AVATAR_INFO;
98+ return CrashHandler::RETAIN_IMPORTANT_INFO;
9699 }
97100 }
98101
@@ -101,24 +104,27 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { · 2 unmodified lines
101104 }
102105
103106 void CrashHandler::handleCrash(CrashHandler::Action action) {
104− if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_AVATAR_INFO) {
107+ if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_IMPORTANT_INFO) {
105108 // CrashHandler::DO_NOTHING or unexpected value
106109 return;
107110 }
108111
109− Settings settings;
112+ QSettings settings;
110113 const QString ADDRESS_MANAGER_GROUP = "AddressManager";
111114 const QString ADDRESS_KEY = "address";
112115 const QString AVATAR_GROUP = "Avatar";
113116 const QString DISPLAY_NAME_KEY = "displayName";
114117 const QString FULL_AVATAR_URL_KEY = "fullAvatarURL";
115118 const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName";
119+ const QString TUTORIAL_COMPLETE_FLAG_KEY = "tutorialComplete";
120+
116121 QString displayName;
117122 QUrl fullAvatarURL;
118123 QString fullAvatarModelName;
119124 QUrl address;
125+ bool tutorialComplete = false;
120126
121− if (action == CrashHandler::RETAIN_AVATAR_INFO) {
127+ if (action == CrashHandler::RETAIN_IMPORTANT_INFO) {
122128 // Read avatar info
123129
124130 // Location and orientation
@@ -132,6 +138,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { · 7 unmodified lines
132138 fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl();
133139 fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString();
134140 settings.endGroup();
141+
142+ // Tutorial complete
143+ tutorialComplete = settings.value(TUTORIAL_COMPLETE_FLAG_KEY).toBool();
135144 }
136145
137146 // Delete Interface.ini
@@ -140,7 +149,7 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { · 2 unmodified lines
140149 settingsFile.remove();
141150 }
142151
143− if (action == CrashHandler::RETAIN_AVATAR_INFO) {
152+ if (action == CrashHandler::RETAIN_IMPORTANT_INFO) {
144153 // Write avatar info
145154
146155 // Location and orientation
@@ -154,6 +163,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { · 7 unmodified lines
154163 settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL);
155164 settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName);
156165 settings.endGroup();
166+
167+ // Tutorial complete
168+ settings.setValue(TUTORIAL_COMPLETE_FLAG_KEY, tutorialComplete);
157169 }
158170 }
159171
interface/src/CrashHandler.h+1−1
@@ -22,7 +22,7 @@ public: · 21 unmodified lines
2222 private:
2323 enum Action {
2424 DELETE_INTERFACE_INI,
25− RETAIN_AVATAR_INFO,
25+ RETAIN_IMPORTANT_INFO,
2626 DO_NOTHING
2727 };
2828
interface/src/DiscoverabilityManager.cpp+1−1
@@ -25,7 +25,7 @@ · 24 unmodified lines
2525
2626 #include <QThread>
2727
28−const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Friends;
28+const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Connections;
2929
3030 DiscoverabilityManager::DiscoverabilityManager() :
3131 _mode("discoverabilityMode", DEFAULT_DISCOVERABILITY_MODE)
interface/src/Menu.cpp+6−0
@@ -405,6 +405,12 @@ Menu::Menu() { · 404 unmodified lines
405405 #endif
406406
407407
408+ {
409+ auto action = addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::RenderClearKtxCache);
410+ connect(action, &QAction::triggered, []{
411+ Setting::Handle<int>(KTXCache::SETTING_VERSION_NAME, KTXCache::INVALID_VERSION).set(KTXCache::INVALID_VERSION);
412+ });
413+ }
408414
409415 // Developer > Render > LOD Tools
410416 addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0,
interface/src/Menu.h+1−0
@@ -142,6 +142,7 @@ namespace MenuOption { · 141 unmodified lines
142142 const QString Quit = "Quit";
143143 const QString ReloadAllScripts = "Reload All Scripts";
144144 const QString ReloadContent = "Reload Content (Clears all caches)";
145+ const QString RenderClearKtxCache = "Clear KTX Cache (requires restart)";
145146 const QString RenderMaxTextureMemory = "Maximum Texture Memory";
146147 const QString RenderMaxTextureAutomatic = "Automatic Texture Memory";
147148 const QString RenderMaxTexture4MB = "4 MB";
interface/src/avatar/MyAvatar.cpp+276−57
@@ -294,6 +294,7 @@ void MyAvatar::simulateAttachments(float deltaTime) { · 293 unmodified lines
294294 QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) {
295295 CameraMode mode = qApp->getCamera().getMode();
296296 _globalPosition = getPosition();
297+ // This might not be right! Isn't the capsule local offset in avatar space, and don't we need to add the radius to the y as well? -HRS 5/26/17
297298 _globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius();
298299 _globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight();
299300 _globalBoundingBoxDimensions.z = _characterController.getCapsuleRadius();
@@ -409,7 +410,7 @@ void MyAvatar::update(float deltaTime) { · 109 unmodified lines
409410 // update moving average of HMD facing in xz plane.
410411 const float HMD_FACING_TIMESCALE = 4.0f; // very slow average
411412 float tau = deltaTime / HMD_FACING_TIMESCALE;
412− _hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau);
413+ _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau);
413414
414415 if (_smoothOrientationTimer < SMOOTH_TIME_ORIENTATION) {
415416 _rotationChanged = usecTimestampNow();
@@ -417,16 +418,18 @@ void MyAvatar::update(float deltaTime) { · 1 unmodified line
417418 }
418419
419420 #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE
420− glm::vec3 p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacingMovingAverage.x, 0.0f, _hmdSensorFacingMovingAverage.y));
421+ glm::vec3 p = transformPoint(getSensorToWorldMatrix(), getHeadControllerPoseInAvatarFrame() *
422+ glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y));
421423 DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f));
422− p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacing.x, 0.0f, _hmdSensorFacing.y));
424+ p = transformPoint(getSensorToWorldMatrix(), getHMDSensorPosition() +
425+ glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y));
423426 DebugDraw::getInstance().addMarker("facing", getOrientation(), p, glm::vec4(1.0f));
424427 #endif
425428
426429 if (_goToPending) {
427430 setPosition(_goToPosition);
428431 setOrientation(_goToOrientation);
429− _hmdSensorFacingMovingAverage = _hmdSensorFacing; // reset moving average
432+ _headControllerFacingMovingAverage = _headControllerFacing; // reset moving average
430433 _goToPending = false;
431434 // updateFromHMDSensorMatrix (called from paintGL) expects that the sensorToWorldMatrix is updated for any position changes
432435 // that happen between render and Application::update (which calls updateSensorToWorldMatrix to do so).
@@ -434,6 +437,13 @@ void MyAvatar::update(float deltaTime) { · 1 unmodified line
434437 // so we update now. It's ok if it updates again in the normal way.
435438 updateSensorToWorldMatrix();
436439 emit positionGoneTo();
440+ // Run safety tests as soon as we can after goToLocation, or clear if we're not colliding.
441+ _physicsSafetyPending = getCollisionsEnabled();
442+ }
443+ if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) {
444+ // When needed and ready, arrange to check and fix.
445+ _physicsSafetyPending = false;
446+ safeLanding(_goToPosition); // no-op if already safe
437447 }
438448
439449 Head* head = getHead();
@@ -448,6 +458,7 @@ void MyAvatar::update(float deltaTime) { · 8 unmodified lines
448458 setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
449459
450460 glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius());
461+ // This might not be right! Isn't the capsule local offset in avatar space? -HRS 5/26/17
451462 halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset();
452463 QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters",
453464 Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)),
@@ -633,15 +644,21 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { · 179 unmodified lines
633644 _hmdSensorMatrix = hmdSensorMatrix;
634645 auto newHmdSensorPosition = extractTranslation(hmdSensorMatrix);
635646
636− if (newHmdSensorPosition != _hmdSensorPosition &&
647+ if (newHmdSensorPosition != getHMDSensorPosition() &&
637648 glm::length(newHmdSensorPosition) > MAX_HMD_ORIGIN_DISTANCE) {
638649 qWarning() << "Invalid HMD sensor position " << newHmdSensorPosition;
639650 // Ignore unreasonable HMD sensor data
640651 return;
641652 }
653+
642654 _hmdSensorPosition = newHmdSensorPosition;
643655 _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix);
644− _hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation);
656+ auto headPose = _headControllerPoseInSensorFrameCache.get();
657+ if (headPose.isValid()) {
658+ _headControllerFacing = getFacingDir2D(headPose.rotation);
659+ } else {
660+ _headControllerFacing = glm::vec2(1.0f, 0.0f);
661+ }
645662 }
646663
647664 void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeValueCache<glm::mat4>& matrixCache) {
@@ -679,7 +696,7 @@ void MyAvatar::updateSensorToWorldMatrix() { · 31 unmodified lines
679696
680697 // Update avatar head rotation with sensor data
681698 void MyAvatar::updateFromTrackers(float deltaTime) {
682− glm::vec3 estimatedPosition, estimatedRotation;
699+ glm::vec3 estimatedRotation;
683700
684701 bool inHmd = qApp->isHMDMode();
685702 bool playing = DependencyManager::get<recording::Deck>()->isPlaying();
@@ -690,11 +707,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) { · 4 unmodified lines
690707 FaceTracker* tracker = qApp->getActiveFaceTracker();
691708 bool inFacetracker = tracker && !FaceTracker::isMuted();
692709
693− if (inHmd) {
694− estimatedPosition = extractTranslation(getHMDSensorMatrix());
695− estimatedPosition.x *= -1.0f;
696− } else if (inFacetracker) {
697− estimatedPosition = tracker->getHeadTranslation();
710+ if (inFacetracker) {
698711 estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation()));
699712 }
700713
@@ -781,6 +794,77 @@ controller::Pose MyAvatar::getRightHandTipPose() const { · 80 unmodified lines
781794 return pose;
782795 }
783796
797+glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const {
798+ glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified
799+ glm::quat jointRot = getRotation();//default value if no or invalid joint specified
800+ if (jointIndex != -1) {
801+ if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
802+ _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
803+ } else {
804+ qWarning() << "Invalid joint index specified: " << jointIndex;
805+ }
806+ }
807+ glm::vec3 modelOffset = position - jointPos;
808+ glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset;
809+
810+ return jointSpacePosition;
811+}
812+
813+glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const {
814+ glm::quat jointRot = getRotation();//default value if no or invalid joint specified
815+ if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
816+ qWarning() << "Invalid joint index specified: " << jointIndex;
817+ }
818+
819+ glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir;
820+ return jointSpaceDir;
821+}
822+
823+glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const {
824+ glm::quat jointRot = getRotation();//default value if no or invalid joint specified
825+ if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
826+ qWarning() << "Invalid joint index specified: " << jointIndex;
827+ }
828+ glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot;
829+ return jointSpaceRot;
830+}
831+
832+glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const {
833+ glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified
834+ glm::quat jointRot = getRotation();//default value if no or invalid joint specified
835+
836+ if (jointIndex != -1) {
837+ if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
838+ _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
839+ } else {
840+ qWarning() << "Invalid joint index specified: " << jointIndex;
841+ }
842+ }
843+
844+ glm::vec3 worldOffset = jointRot * jointSpacePos;
845+ glm::vec3 worldPos = jointPos + worldOffset;
846+
847+ return worldPos;
848+}
849+
850+glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const {
851+ glm::quat jointRot = getRotation();//default value if no or invalid joint specified
852+ if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
853+ qWarning() << "Invalid joint index specified: " << jointIndex;
854+ }
855+ glm::vec3 worldDir = jointRot * jointSpaceDir;
856+ return worldDir;
857+}
858+
859+glm::quat MyAvatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const {
860+ glm::quat jointRot = getRotation();//default value if no or invalid joint specified
861+ if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
862+ qWarning() << "Invalid joint index specified: " << jointIndex;
863+ }
864+ glm::quat worldRot = jointRot * jointSpaceRot;
865+ return worldRot;
866+}
867+
784868 // virtual
785869 void MyAvatar::render(RenderArgs* renderArgs) {
786870 // don't render if we've been asked to disable local rendering
@@ -1479,12 +1563,12 @@ void MyAvatar::updateMotors() { · 692 unmodified lines
14791563 if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
14801564 if (_characterController.getState() == CharacterController::State::Hover ||
14811565 _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
1482− motorRotation = getMyHead()->getCameraOrientation();
1566+ motorRotation = getMyHead()->getHeadOrientation();
14831567 } else {
14841568 // non-hovering = walking: follow camera twist about vertical but not lift
14851569 // so we decompose camera's rotation and store the twist part in motorRotation
14861570 glm::quat liftRotation;
1487− swingTwistDecomposition(getMyHead()->getCameraOrientation(), _worldUpDirection, liftRotation, motorRotation);
1571+ swingTwistDecomposition(getMyHead()->getHeadOrientation(), _worldUpDirection, liftRotation, motorRotation);
14881572 }
14891573 const float DEFAULT_MOTOR_TIMESCALE = 0.2f;
14901574 const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
@@ -1498,7 +1582,7 @@ void MyAvatar::updateMotors() { · 7 unmodified lines
14981582 }
14991583 if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) {
15001584 if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) {
1501− motorRotation = getMyHead()->getCameraOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
1585+ motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
15021586 } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) {
15031587 motorRotation = getOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
15041588 } else {
@@ -1548,6 +1632,10 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { · 43 unmodified lines
15481632
15491633 if (_characterController.isEnabledAndReady()) {
15501634 setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
1635+ if (_characterController.isStuck()) {
1636+ _physicsSafetyPending = true;
1637+ _goToPosition = getPosition();
1638+ }
15511639 } else {
15521640 setVelocity(getVelocity() + _characterController.getFollowVelocity());
15531641 }
@@ -1847,7 +1935,7 @@ void MyAvatar::updateOrientation(float deltaTime) { · 293 unmodified lines
18471935 if (getCharacterController()->getState() == CharacterController::State::Hover) {
18481936
18491937 // This is the direction the user desires to fly in.
1850− glm::vec3 desiredFacing = getMyHead()->getCameraOrientation() * Vectors::UNIT_Z;
1938+ glm::vec3 desiredFacing = getMyHead()->getHeadOrientation() * Vectors::UNIT_Z;
18511939 desiredFacing.y = 0.0f;
18521940
18531941 // This is our reference frame, it is captured when the user begins to move.
@@ -1886,11 +1974,9 @@ void MyAvatar::updateOrientation(float deltaTime) { · 32 unmodified lines
18861974
18871975 getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime);
18881976
1889− if (qApp->isHMDMode()) {
1890− glm::quat orientation = glm::quat_cast(getSensorToWorldMatrix()) * getHMDSensorOrientation();
1891− glm::quat bodyOrientation = getWorldBodyOrientation();
1892− glm::quat localOrientation = glm::inverse(bodyOrientation) * orientation;
1893−
1977+ auto headPose = getHeadControllerPoseInAvatarFrame();
1978+ if (headPose.isValid()) {
1979+ glm::quat localOrientation = headPose.rotation * Quaternions::Y_180;
18941980 // these angles will be in radians
18951981 // ... so they need to be converted to degrees before we do math...
18961982 glm::vec3 euler = glm::eulerAngles(localOrientation) * DEGREES_PER_RADIAN;
@@ -2004,11 +2090,14 @@ void MyAvatar::updatePosition(float deltaTime) { · 107 unmodified lines
20042090 }
20052091
20062092 // capture the head rotation, in sensor space, when the user first indicates they would like to move/fly.
2007− if (!_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) {
2093+ if (!_hoverReferenceCameraFacingIsCaptured &&
2094+ (fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) {
20082095 _hoverReferenceCameraFacingIsCaptured = true;
20092096 // transform the camera facing vector into sensor space.
2010− _hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix), getMyHead()->getCameraOrientation() * Vectors::UNIT_Z);
2011− } else if (_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) {
2097+ _hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix),
2098+ getMyHead()->getHeadOrientation() * Vectors::UNIT_Z);
2099+ } else if (_hoverReferenceCameraFacingIsCaptured &&
2100+ (fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) {
20122101 _hoverReferenceCameraFacingIsCaptured = false;
20132102 }
20142103 }
@@ -2219,6 +2308,144 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, · 204 unmodified lines
22192308 emit transformChanged();
22202309 }
22212310
2311+void MyAvatar::goToLocationAndEnableCollisions(const glm::vec3& position) { // See use case in safeLanding.
2312+ goToLocation(position);
2313+ QMetaObject::invokeMethod(this, "setCollisionsEnabled", Qt::QueuedConnection, Q_ARG(bool, true));
2314+}
2315+bool MyAvatar::safeLanding(const glm::vec3& position) {
2316+ // Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point.
2317+ // There needs to be a "landing" if:
2318+ // a) the closest above and the closest below are less than the avatar capsule height apart, or
2319+ // b) the above point is the top surface of an entity, indicating that we are inside it.
2320+ // If no landing is required, we go to that point directly and return false;
2321+ // When a landing is required by a, we find the highest intersection on that closest-agbove entity
2322+ // (which may be that same "nearest above intersection"). That highest intersection is the candidate landing point.
2323+ // For b, use that top surface point.
2324+ // We then place our feet there, recurse with new capsule center point, and return true.
2325+
2326+ if (QThread::currentThread() != thread()) {
2327+ bool result;
2328+ QMetaObject::invokeMethod(this, "safeLanding", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(const glm::vec3&, position));
2329+ return result;
2330+ }
2331+ glm::vec3 better;
2332+ if (!requiresSafeLanding(position, better)) {
2333+ return false;
2334+ }
2335+ if (!getCollisionsEnabled()) {
2336+ goToLocation(better); // recurses on next update
2337+ } else { // If you try to go while stuck, physics will keep you stuck.
2338+ setCollisionsEnabled(false);
2339+ // Don't goToLocation just yet. Yield so that physics can act on the above.
2340+ QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick
2341+ Q_ARG(glm::vec3, better));
2342+ }
2343+ return true;
2344+}
2345+
2346+// If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut.
2347+bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) {
2348+ // We begin with utilities and tests. The Algorithm in four parts is below.
2349+ auto halfHeight = _characterController.getCapsuleHalfHeight() + _characterController.getCapsuleRadius();
2350+ if (halfHeight == 0) {
2351+ return false; // zero height avatar
2352+ }
2353+ auto entityTree = DependencyManager::get<EntityTreeRenderer>()->getTree();
2354+ if (!entityTree) {
2355+ return false; // no entity tree
2356+ }
2357+ // More utilities.
2358+ const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset();
2359+ const auto capsuleCenter = positionIn + offset;
2360+ const auto up = _worldUpDirection, down = -up;
2361+ glm::vec3 upperIntersection, upperNormal, lowerIntersection, lowerNormal;
2362+ EntityItemID upperId, lowerId;
2363+ QVector<EntityItemID> include{}, ignore{};
2364+ auto mustMove = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center.
2365+ betterPositionOut = upperIntersection + (up * halfHeight) - offset;
2366+ return true;
2367+ };
2368+ auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) {
2369+ OctreeElementPointer element;
2370+ EntityItemPointer intersectedEntity = NULL;
2371+ float distance;
2372+ BoxFace face;
2373+ const bool visibleOnly = false;
2374+ // This isn't quite what we really want here. findRayIntersection always works on mesh, skipping entirely based on collidable.
2375+ // What we really want is to use the collision hull!
2376+ // See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders
2377+ const bool collidableOnly = true;
2378+ const bool precisionPicking = true;
2379+ const auto lockType = Octree::Lock; // Should we refactor to take a lock just once?
2380+ bool* accurateResult = NULL;
2381+
2382+ bool intersects = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking,
2383+ element, distance, face, normalOut, (void**)&intersectedEntity, lockType, accurateResult);
2384+ if (!intersects || !intersectedEntity) {
2385+ return false;
2386+ }
2387+ intersectionOut = startPointIn + (directionIn * distance);
2388+ entityIdOut = intersectedEntity->getEntityItemID();
2389+ return true;
2390+ };
2391+
2392+ // The Algorithm, in four parts:
2393+
2394+ if (!findIntersection(capsuleCenter, up, upperIntersection, upperId, upperNormal)) {
2395+ // We currently believe that physics will reliably push us out if our feet are embedded,
2396+ // as long as our capsule center is out and there's room above us. Here we have those
2397+ // conditions, so no need to check our feet below.
2398+ return false; // nothing above
2399+ }
2400+
2401+ if (!findIntersection(capsuleCenter, down, lowerIntersection, lowerId, lowerNormal)) {
2402+ // Our head may be embedded, but our center is out and there's room below. See corresponding comment above.
2403+ return false; // nothing below
2404+ }
2405+
2406+ // See if we have room between entities above and below, but that we are not contained.
2407+ // First check if the surface above us is the bottom of something, and the surface below us it the top of something.
2408+ // I.e., we are in a clearing between two objects.
2409+ if (isDown(upperNormal) && isUp(lowerNormal)) {
2410+ auto spaceBetween = glm::distance(upperIntersection, lowerIntersection);
2411+ const float halfHeightFactor = 2.5f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise.
2412+ if (spaceBetween > (halfHeightFactor * halfHeight)) {
2413+ // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below.
2414+ // We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity.
2415+ // There will be one of two outcomes:
2416+ // a) We're not contained, so we have enough room and our position is good.
2417+ // b) We are contained, so we'll bail out of this but try again at a position above the containing entity.
2418+ const int iterationLimit = 1000;
2419+ for (int counter = 0; counter < iterationLimit; counter++) {
2420+ ignore.push_back(upperId);
2421+ if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) {
2422+ // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good!
2423+ return false; // enough room
2424+ }
2425+ if (isUp(upperNormal)) {
2426+ // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it.
2427+ // We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top
2428+ // of the enclosing entity, let's put our feet at upperIntersection and start over.
2429+ return mustMove();
2430+ }
2431+ // We found a new bottom surface, which we're not interested in.
2432+ // But there could still be a top surface above us for an entity we haven't seen, so keep looking upward.
2433+ }
2434+ qCDebug(interfaceapp) << "Loop in requiresSafeLanding. Floor/ceiling do not make sense.";
2435+ }
2436+ }
2437+
2438+ include.push_back(upperId); // We're now looking for the intersection from above onto this entity.
2439+ const float big = (float)TREE_SCALE;
2440+ const auto skyHigh = up * big;
2441+ auto fromAbove = capsuleCenter + skyHigh;
2442+ if (!findIntersection(fromAbove, down, upperIntersection, upperId, upperNormal)) {
2443+ return false; // Unable to find a landing
2444+ }
2445+ // Our arbitrary rule is to always go up. There's no need to look down or sideways for a "closer" safe candidate.
2446+ return mustMove();
2447+}
2448+
22222449 void MyAvatar::updateMotionBehaviorFromMenu() {
22232450
22242451 if (QThread::currentThread() != thread()) {
@@ -2322,36 +2549,27 @@ bool MyAvatar::isDriveKeyDisabled(DriveKeys key) const { · 97 unmodified lines
23222549 }
23232550 }
23242551
2325−glm::vec3 MyAvatar::getWorldBodyPosition() const {
2326− return transformPoint(_sensorToWorldMatrix, extractTranslation(_bodySensorMatrix));
2327−}
2328−
2329−glm::quat MyAvatar::getWorldBodyOrientation() const {
2330− return glm::quat_cast(_sensorToWorldMatrix * _bodySensorMatrix);
2331−}
2332−
23332552 // old school meat hook style
23342553 glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
2335−
2336− // HMD is in sensor space.
2337− const glm::vec3 hmdPosition = getHMDSensorPosition();
2338− const glm::quat hmdOrientation = getHMDSensorOrientation();
2339− const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation);
2554+ glm::vec3 headPosition;
2555+ glm::quat headOrientation;
2556+ auto headPose = getHeadControllerPoseInSensorFrame();
2557+ if (headPose.isValid()) {
2558+ headPosition = getHeadControllerPoseInSensorFrame().translation;
2559+ headOrientation = getHeadControllerPoseInSensorFrame().rotation * Quaternions::Y_180;
2560+ }
2561+ const glm::quat headOrientationYawOnly = cancelOutRollAndPitch(headOrientation);
23402562
23412563 const Rig& rig = _skeletonModel->getRig();
2342− int rightEyeIndex = rig.indexOfJoint("RightEye");
2343− int leftEyeIndex = rig.indexOfJoint("LeftEye");
2564+ int headIndex = rig.indexOfJoint("Head");
23442565 int neckIndex = rig.indexOfJoint("Neck");
23452566 int hipsIndex = rig.indexOfJoint("Hips");
23462567
2347− glm::vec3 rigMiddleEyePos = DEFAULT_AVATAR_MIDDLE_EYE_POS;
2348− if (leftEyeIndex >= 0 && rightEyeIndex >= 0) {
2349− rigMiddleEyePos = (rig.getAbsoluteDefaultPose(leftEyeIndex).trans() + rig.getAbsoluteDefaultPose(rightEyeIndex).trans()) / 2.0f;
2350− }
2568+ glm::vec3 rigHeadPos = headIndex != -1 ? rig.getAbsoluteDefaultPose(headIndex).trans() : DEFAULT_AVATAR_HEAD_POS;
23512569 glm::vec3 rigNeckPos = neckIndex != -1 ? rig.getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS;
23522570 glm::vec3 rigHipsPos = hipsIndex != -1 ? rig.getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS;
23532571
2354− glm::vec3 localEyes = (rigMiddleEyePos - rigHipsPos);
2572+ glm::vec3 localHead = (rigHeadPos - rigHipsPos);
23552573 glm::vec3 localNeck = (rigNeckPos - rigHipsPos);
23562574
23572575 // apply simplistic head/neck model
@@ -2360,11 +2578,11 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { · 2 unmodified lines
23602578 // eyeToNeck offset is relative full HMD orientation.
23612579 // while neckToRoot offset is only relative to HMDs yaw.
23622580 // Y_180 is necessary because rig is z forward and hmdOrientation is -z forward
2363− glm::vec3 eyeToNeck = hmdOrientation * Quaternions::Y_180 * (localNeck - localEyes);
2364− glm::vec3 neckToRoot = hmdOrientationYawOnly * Quaternions::Y_180 * -localNeck;
2365− glm::vec3 bodyPos = hmdPosition + eyeToNeck + neckToRoot;
2581+ glm::vec3 headToNeck = headOrientation * Quaternions::Y_180 * (localNeck - localHead);
2582+ glm::vec3 neckToRoot = headOrientationYawOnly * Quaternions::Y_180 * -localNeck;
2583+ glm::vec3 bodyPos = headPosition + headToNeck + neckToRoot;
23662584
2367− return createMatFromQuatAndPos(hmdOrientationYawOnly, bodyPos);
2585+ return createMatFromQuatAndPos(headOrientationYawOnly, bodyPos);
23682586 }
23692587
23702588 glm::vec3 MyAvatar::getPositionForAudio() {
@@ -2480,7 +2698,7 @@ bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, co · 109 unmodified lines
24802698 } else {
24812699 const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees
24822700 glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
2483− return glm::dot(myAvatar.getHMDSensorFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
2701+ return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
24842702 }
24852703 }
24862704
@@ -2517,8 +2735,8 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co · 30 unmodified lines
25172735 return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM);
25182736 }
25192737
2520−void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
2521− _desiredBodyMatrix = desiredBodyMatrix;
2738+void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix,
2739+ const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
25222740
25232741 if (myAvatar.getHMDLeanRecenterEnabled()) {
25242742 if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
@@ -2532,7 +2750,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat · 7 unmodified lines
25322750 }
25332751 }
25342752
2535− glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix;
2753+ glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * desiredBodyMatrix;
25362754 glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix;
25372755
25382756 AnimPose followWorldPose(currentWorldMatrix);
@@ -2627,9 +2845,10 @@ glm::mat4 MyAvatar::computeCameraRelativeHandControllerMatrix(const glm::mat4& c · 88 unmodified lines
26272845 cameraWorldMatrix *= createMatFromScaleQuatAndPos(vec3(-1.0f, 1.0f, 1.0f), glm::quat(), glm::vec3());
26282846 }
26292847
2630− // compute a NEW sensorToWorldMatrix for the camera. The equation is cameraWorldMatrix = cameraSensorToWorldMatrix * _hmdSensorMatrix.
2848+ // compute a NEW sensorToWorldMatrix for the camera.
2849+ // The equation is cameraWorldMatrix = cameraSensorToWorldMatrix * _hmdSensorMatrix.
26312850 // here we solve for the unknown cameraSensorToWorldMatrix.
2632− glm::mat4 cameraSensorToWorldMatrix = cameraWorldMatrix * glm::inverse(_hmdSensorMatrix);
2851+ glm::mat4 cameraSensorToWorldMatrix = cameraWorldMatrix * glm::inverse(getHMDSensorMatrix());
26332852
26342853 // Using the new cameraSensorToWorldMatrix, compute where the controller is in world space.
26352854 glm::mat4 controllerWorldMatrix = cameraSensorToWorldMatrix * controllerSensorMatrix;
@@ -2770,7 +2989,7 @@ glm::mat4 MyAvatar::getLeftFootCalibrationMat() const { · 134 unmodified lines
27702989 auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex);
27712990 return createMatFromQuatAndPos(leftFootRot, leftFootPos);
27722991 } else {
2773− return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_POS, DEFAULT_AVATAR_LEFTFOOT_POS);
2992+ return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS);
27742993 }
27752994 }
27762995
@@ -2782,7 +3001,7 @@ glm::mat4 MyAvatar::getRightFootCalibrationMat() const { · 5 unmodified lines
27823001 auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex);
27833002 return createMatFromQuatAndPos(rightFootRot, rightFootPos);
27843003 } else {
2785− return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_POS, DEFAULT_AVATAR_RIGHTFOOT_POS);
3004+ return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS);
27863005 }
27873006 }
27883007
@@ -2805,7 +3024,7 @@ glm::mat4 MyAvatar::getLeftArmCalibrationMat() const { · 16 unmodified lines
28053024 auto leftArmRot = getAbsoluteDefaultJointRotationInObjectFrame(leftArmIndex);
28063025 return createMatFromQuatAndPos(leftArmRot, leftArmPos);
28073026 } else {
2808− return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS);
3027+ return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS);
28093028 }
28103029 }
28113030
interface/src/avatar/MyAvatar.h+24−10
@@ -185,7 +185,6 @@ public: · 184 unmodified lines
185185 const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; }
186186 const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; }
187187 const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
188− const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; }
189188
190189 Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar);
191190 Q_INVOKABLE QVariant getOrientationVar() const;
@@ -379,6 +378,15 @@ public: · 187 unmodified lines
379378 Q_INVOKABLE controller::Pose getLeftHandTipPose() const;
380379 Q_INVOKABLE controller::Pose getRightHandTipPose() const;
381380
381+ // world-space to avatar-space rigconversion functions
382+ Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const;
383+ Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const;
384+ Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const;
385+
386+ Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const;
387+ Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const;
388+ Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const;
389+
382390 AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; }
383391 void updateLookAtTargetAvatar();
384392 void clearLookAtTargetAvatar();
@@ -470,6 +478,8 @@ public: · 85 unmodified lines
470478 controller::Pose getHeadControllerPoseInSensorFrame() const;
471479 controller::Pose getHeadControllerPoseInWorldFrame() const;
472480 controller::Pose getHeadControllerPoseInAvatarFrame() const;
481+ const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; }
482+
473483
474484 void setArmControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right);
475485 controller::Pose getLeftArmControllerPoseInSensorFrame() const;
@@ -509,6 +519,10 @@ public: · 33 unmodified lines
509519 // results are in HMD frame
510520 glm::mat4 deriveBodyFromHMDSensor() const;
511521
522+ Q_INVOKABLE bool isUp(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up.
523+ Q_INVOKABLE bool isDown(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; };
524+
525+
512526 public slots:
513527 void increaseSize();
514528 void decreaseSize();
@@ -518,6 +532,8 @@ public slots: · 3 unmodified lines
518532 bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(),
519533 bool shouldFaceLocation = false);
520534 void goToLocation(const QVariant& properties);
535+ void goToLocationAndEnableCollisions(const glm::vec3& newPosition);
536+ bool safeLanding(const glm::vec3& position);
521537
522538 void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject);
523539 void clearScaleRestriction();
@@ -563,9 +579,7 @@ signals: · 39 unmodified lines
563579
564580 private:
565581
566− glm::vec3 getWorldBodyPosition() const;
567− glm::quat getWorldBodyOrientation() const;
568−
582+ bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut);
569583
570584 virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override;
571585
@@ -676,13 +690,13 @@ private: · 104 unmodified lines
676690 // working copies -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
677691 glm::mat4 _sensorToWorldMatrix { glm::mat4() };
678692
679− // cache of the current HMD sensor position and orientation
680− // in sensor space.
693+ // cache of the current HMD sensor position and orientation in sensor space.
681694 glm::mat4 _hmdSensorMatrix;
682695 glm::quat _hmdSensorOrientation;
683696 glm::vec3 _hmdSensorPosition;
684− glm::vec2 _hmdSensorFacing; // facing vector in xz plane
685− glm::vec2 _hmdSensorFacingMovingAverage { 0, 0 }; // facing vector in xz plane
697+ // cache head controller pose in sensor space
698+ glm::vec2 _headControllerFacing; // facing vector in xz plane
699+ glm::vec2 _headControllerFacingMovingAverage { 0, 0 }; // facing vector in xz plane
686700
687701 // cache of the current body position and orientation of the avatar's body,
688702 // in sensor space.
@@ -697,7 +711,6 @@ private: · 8 unmodified lines
697711 Vertical,
698712 NumFollowTypes
699713 };
700− glm::mat4 _desiredBodyMatrix;
701714 float _timeRemaining[NumFollowTypes];
702715
703716 void deactivate();
@@ -716,7 +729,8 @@ private: · 12 unmodified lines
716729 };
717730 FollowHelper _follow;
718731
719− bool _goToPending;
732+ bool _goToPending { false };
733+ bool _physicsSafetyPending { false };
720734 glm::vec3 _goToPosition;
721735 glm::quat _goToOrientation;
722736
interface/src/avatar/MyHead.cpp+9−8
@@ -26,19 +26,20 @@ using namespace std; · 25 unmodified lines
2626 MyHead::MyHead(MyAvatar* owningAvatar) : Head(owningAvatar) {
2727 }
2828
29−glm::quat MyHead::getCameraOrientation() const {
30− // NOTE: Head::getCameraOrientation() is not used for orienting the camera "view" while in Oculus mode, so
29+glm::quat MyHead::getHeadOrientation() const {
30+ // NOTE: Head::getHeadOrientation() is not used for orienting the camera "view" while in Oculus mode, so
3131 // you may wonder why this code is here. This method will be called while in Oculus mode to determine how
3232 // to change the driving direction while in Oculus mode. It is used to support driving toward where you're
3333 // head is looking. Note that in oculus mode, your actual camera view and where your head is looking is not
3434 // always the same.
35− if (qApp->isHMDMode()) {
36− MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
37− return glm::quat_cast(myAvatar->getSensorToWorldMatrix()) * myAvatar->getHMDSensorOrientation();
38− } else {
39− Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar);
40− return owningAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
35+
36+ MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
37+ auto headPose = myAvatar->getHeadControllerPoseInWorldFrame();
38+ if (headPose.isValid()) {
39+ return headPose.rotation * Quaternions::Y_180;
4140 }
41+
42+ return myAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
4243 }
4344
4445 void MyHead::simulate(float deltaTime) {
interface/src/avatar/MyHead.h+1−1
@@ -18,7 +18,7 @@ public: · 17 unmodified lines
1818 explicit MyHead(MyAvatar* owningAvatar);
1919
2020 /// \return orientationBody * orientationBasePitch
21− glm::quat getCameraOrientation() const;
21+ glm::quat getHeadOrientation() const;
2222 void simulate(float deltaTime) override;
2323
2424 private:
interface/src/avatar/MySkeletonModel.cpp+8−16
@@ -52,26 +52,18 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { · 51 unmodified lines
5252 // input action is the highest priority source for head orientation.
5353 auto avatarHeadPose = myAvatar->getHeadControllerPoseInAvatarFrame();
5454 if (avatarHeadPose.isValid()) {
55− glm::mat4 rigHeadMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation());
55+ glm::mat4 rigHeadMat = Matrices::Y_180 *
56+ createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation());
5657 headParams.rigHeadPosition = extractTranslation(rigHeadMat);
5758 headParams.rigHeadOrientation = glmExtractRotation(rigHeadMat);
5859 headParams.headEnabled = true;
5960 } else {
60− if (qApp->isHMDMode()) {
61− // get HMD position from sensor space into world space, and back into rig space
62− glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
63− glm::mat4 rigToWorld = createMatFromQuatAndPos(getRotation(), getTranslation());
64− glm::mat4 worldToRig = glm::inverse(rigToWorld);
65− glm::mat4 rigHMDMat = worldToRig * worldHMDMat;
66− _rig.computeHeadFromHMD(AnimPose(rigHMDMat), headParams.rigHeadPosition, headParams.rigHeadOrientation);
67− headParams.headEnabled = true;
68− } else {
69− // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and down in desktop mode.
70− // preMult 180 is necessary to convert from avatar to rig coordinates.
71− // postMult 180 is necessary to convert head from -z forward to z forward.
72− headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180;
73− headParams.headEnabled = false;
74− }
61+ // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and
62+ // down in desktop mode.
63+ // preMult 180 is necessary to convert from avatar to rig coordinates.
64+ // postMult 180 is necessary to convert head from -z forward to z forward.
65+ headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180;
66+ headParams.headEnabled = false;
7567 }
7668
7769 auto avatarHipsPose = myAvatar->getHipsControllerPoseInAvatarFrame();
interface/src/main.cpp+2−6
@@ -24,7 +24,6 @@ · 23 unmodified lines
2424 #include <SandboxUtils.h>
2525 #include <SharedUtil.h>
2626
27−
2827 #include "AddressManager.h"
2928 #include "Application.h"
3029 #include "InterfaceLogging.h"
@@ -191,7 +190,7 @@ int main(int argc, const char* argv[]) { · 160 unmodified lines
191190
192191 int exitCode;
193192 {
194− RunningMarker runningMarker(nullptr, RUNNING_MARKER_FILENAME);
193+ RunningMarker runningMarker(RUNNING_MARKER_FILENAME);
195194 bool runningMarkerExisted = runningMarker.fileExists();
196195 runningMarker.writeRunningMarkerFile();
197196
@@ -200,14 +199,11 @@ int main(int argc, const char* argv[]) { · 2 unmodified lines
200199 bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
201200 QString serverContentPath = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
202201 if (runServer) {
203− SandboxUtils::runLocalSandbox(serverContentPath, true, RUNNING_MARKER_FILENAME, noUpdater);
202+ SandboxUtils::runLocalSandbox(serverContentPath, true, noUpdater);
204203 }
205204
206205 Application app(argc, const_cast<char**>(argv), startupTime, runningMarkerExisted);
207206
208− // Now that the main event loop is setup, launch running marker thread
209− runningMarker.startRunningMarker();
210−
211207 // If we failed the OpenGLVersion check, log it.
212208 if (override) {
213209 auto accountManager = DependencyManager::get<AccountManager>();
interface/src/networking/CloseEventSender.cpp+95−0
@@ -0,0 +1,95 @@
1+//
2+// CloseEventSender.cpp
3+// interface/src/networking
4+//
5+// Created by Stephen Birarda on 5/31/17.
6+// Copyright 2017 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+
12+#include <QtCore/QDateTime>
13+#include <QtCore/QEventLoop>
14+#include <QtCore/QJsonDocument>
15+#include <QtNetwork/QNetworkReply>
16+
17+#include <ThreadHelpers.h>
18+#include <AccountManager.h>
19+#include <NetworkAccessManager.h>
20+#include <NetworkingConstants.h>
21+#include <NetworkLogging.h>
22+#include <UserActivityLogger.h>
23+#include <UUID.h>
24+
25+#include "CloseEventSender.h"
26+
27+QNetworkRequest createNetworkRequest() {
28+
29+ QNetworkRequest request;
30+
31+ QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
32+ requestURL.setPath(USER_ACTIVITY_URL);
33+
34+ request.setUrl(requestURL);
35+
36+ auto accountManager = DependencyManager::get<AccountManager>();
37+
38+ if (accountManager->hasValidAccessToken()) {
39+ request.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,
40+ accountManager->getAccountInfo().getAccessToken().authorizationHeaderValue());
41+ }
42+
43+ request.setRawHeader(METAVERSE_SESSION_ID_HEADER,
44+ uuidStringWithoutCurlyBraces(accountManager->getSessionID()).toLocal8Bit());
45+
46+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
47+
48+ request.setPriority(QNetworkRequest::HighPriority);
49+
50+ return request;
51+}
52+
53+QByteArray postDataForAction(QString action) {
54+ return QString("{\"action_name\": \"" + action + "\"}").toUtf8();
55+}
56+
57+QNetworkReply* replyForAction(QString action) {
58+ QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
59+ return networkAccessManager.post(createNetworkRequest(), postDataForAction(action));
60+}
61+
62+void CloseEventSender::sendQuitEventAsync() {
63+ if (UserActivityLogger::getInstance().isEnabled()) {
64+ QNetworkReply* reply = replyForAction("quit");
65+ connect(reply, &QNetworkReply::finished, this, &CloseEventSender::handleQuitEventFinished);
66+ _quitEventStartTimestamp = QDateTime::currentMSecsSinceEpoch();
67+ } else {
68+ _hasFinishedQuitEvent = true;
69+ }
70+}
71+
72+void CloseEventSender::handleQuitEventFinished() {
73+ _hasFinishedQuitEvent = true;
74+
75+ auto reply = qobject_cast<QNetworkReply*>(sender());
76+ if (reply->error() == QNetworkReply::NoError) {
77+ qCDebug(networking) << "Quit event sent successfully";
78+ } else {
79+ qCDebug(networking) << "Failed to send quit event -" << reply->errorString();
80+ }
81+
82+ reply->deleteLater();
83+}
84+
85+bool CloseEventSender::hasTimedOutQuitEvent() {
86+ const int CLOSURE_EVENT_TIMEOUT_MS = 5000;
87+ return _quitEventStartTimestamp != 0
88+ && QDateTime::currentMSecsSinceEpoch() - _quitEventStartTimestamp > CLOSURE_EVENT_TIMEOUT_MS;
89+}
90+
91+void CloseEventSender::startThread() {
92+ moveToNewNamedThread(this, "CloseEvent Logger Thread", [this] {
93+ sendQuitEventAsync();
94+ });
95+}
interface/src/networking/CloseEventSender.h+42−0
@@ -0,0 +1,42 @@
1+//
2+// CloseEventSender.h
3+// interface/src/networking
4+//
5+// Created by Stephen Birarda on 5/31/17.
6+// Copyright 2017 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+
12+#ifndef hifi_CloseEventSender_h
13+#define hifi_CloseEventSender_h
14+
15+#include <atomic>
16+
17+#include <QtCore/QString>
18+#include <QtCore/QUuid>
19+
20+#include <DependencyManager.h>
21+
22+class CloseEventSender : public QObject, public Dependency {
23+ Q_OBJECT
24+ SINGLETON_DEPENDENCY
25+
26+public:
27+ void startThread();
28+ bool hasTimedOutQuitEvent();
29+ bool hasFinishedQuitEvent() { return _hasFinishedQuitEvent; }
30+
31+public slots:
32+ void sendQuitEventAsync();
33+
34+private slots:
35+ void handleQuitEventFinished();
36+
37+private:
38+ std::atomic<bool> _hasFinishedQuitEvent { false };
39+ std::atomic<int64_t> _quitEventStartTimestamp;
40+};
41+
42+#endif // hifi_CloseEventSender_h
interface/src/scripting/HMDScriptingInterface.cpp+4−0
@@ -54,6 +54,10 @@ bool HMDScriptingInterface::isHMDAvailable(const QString& name) { · 53 unmodified lines
5454 return PluginUtils::isHMDAvailable(name);
5555 }
5656
57+bool HMDScriptingInterface::isHeadControllerAvailable(const QString& name) {
58+ return PluginUtils::isHeadControllerAvailable(name);
59+}
60+
5761 bool HMDScriptingInterface::isHandControllerAvailable(const QString& name) {
5862 return PluginUtils::isHandControllerAvailable(name);
5963 }
interface/src/scripting/HMDScriptingInterface.h+1−0
@@ -43,6 +43,7 @@ public: · 42 unmodified lines
4343 Q_INVOKABLE QString preferredAudioOutput() const;
4444
4545 Q_INVOKABLE bool isHMDAvailable(const QString& name = "");
46+ Q_INVOKABLE bool isHeadControllerAvailable(const QString& name = "");
4647 Q_INVOKABLE bool isHandControllerAvailable(const QString& name = "");
4748 Q_INVOKABLE bool isSubdeviceContainingNameAvailable(const QString& name);
4849
interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp+5−4
@@ -9,9 +9,12 @@ · 8 unmodified lines
99 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
1010 //
1111
12+#include <QtConcurrent/QtConcurrentRun>
13+
14+#include <ThreadHelpers.h>
1215 #include <src/InterfaceLogging.h>
1316 #include <src/ui/AvatarInputs.h>
14−#include <QtConcurrent/QtConcurrentRun>
17+
1518 #include "LimitlessVoiceRecognitionScriptingInterface.h"
1619
1720 const float LimitlessVoiceRecognitionScriptingInterface::_audioLevelThreshold = 0.33f;
@@ -24,9 +27,7 @@ LimitlessVoiceRecognitionScriptingInterface::LimitlessVoiceRecognitionScriptingI · 6 unmodified lines
2427 connect(&_voiceTimer, &QTimer::timeout, this, &LimitlessVoiceRecognitionScriptingInterface::voiceTimeout);
2528 connect(&_connection, &LimitlessConnection::onReceivedTranscription, this, [this](QString transcription){emit onReceivedTranscription(transcription);});
2629 connect(&_connection, &LimitlessConnection::onFinishedSpeaking, this, [this](QString transcription){emit onFinishedSpeaking(transcription);});
27− _connection.moveToThread(&_connectionThread);
28− _connectionThread.setObjectName("Limitless Connection");
29− _connectionThread.start();
30+ moveToNewNamedThread(&_connection, "Limitless Connection");
3031 }
3132
3233 void LimitlessVoiceRecognitionScriptingInterface::update() {
interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h+0−1
@@ -41,7 +41,6 @@ private: · 40 unmodified lines
4141 static const int _voiceTimeoutDuration;
4242
4343 QTimer _voiceTimer;
44− QThread _connectionThread;
4544 LimitlessConnection _connection;
4645
4746 void voiceTimeout();
interface/src/ui/ModelsBrowser.cpp+2−6
@@ -23,6 +23,7 @@ · 22 unmodified lines
2323 #include <QUrlQuery>
2424 #include <QXmlStreamReader>
2525
26+#include <ThreadHelpers.h>
2627 #include <NetworkAccessManager.h>
2728 #include <SharedUtil.h>
2829
@@ -84,12 +85,7 @@ ModelsBrowser::ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent) : · 55 unmodified lines
8485 _handler->connect(this, SIGNAL(destroyed()), SLOT(exit()));
8586
8687 // Setup and launch update thread
87− QThread* thread = new QThread();
88− thread->setObjectName("Models Browser");
89− thread->connect(_handler, SIGNAL(destroyed()), SLOT(quit()));
90− thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
91− _handler->moveToThread(thread);
92− thread->start();
88+ moveToNewNamedThread(_handler, "Models Browser");
9389 emit startDownloading();
9490
9591 // Initialize the view
interface/src/ui/overlays/Text3DOverlay.cpp+13−3
@@ -51,6 +51,16 @@ Text3DOverlay::~Text3DOverlay() { · 50 unmodified lines
5151 }
5252 }
5353
54+const QString Text3DOverlay::getText() const {
55+ QMutexLocker lock(&_mutex);
56+ return _text;
57+}
58+
59+void Text3DOverlay::setText(const QString& text) {
60+ QMutexLocker lock(&_mutex);
61+ _text = text;
62+}
63+
5464 xColor Text3DOverlay::getBackgroundColor() {
5565 if (_colorPulse == 0.0f) {
5666 return _backgroundColor;
@@ -125,7 +135,7 @@ void Text3DOverlay::render(RenderArgs* args) { · 68 unmodified lines
125135 // FIXME: Factor out textRenderer so that Text3DOverlay overlay parts can be grouped by pipeline
126136 // for a gpu performance increase. Currently,
127137 // Text renderer sets its own pipeline,
128− _textRenderer->draw(batch, 0, 0, _text, textColor, glm::vec2(-1.0f), getDrawInFront());
138+ _textRenderer->draw(batch, 0, 0, getText(), textColor, glm::vec2(-1.0f), getDrawInFront());
129139 // so before we continue, we must reset the pipeline
130140 batch.setPipeline(args->_pipeline->pipeline);
131141 args->_pipeline->prepare(batch);
@@ -188,7 +198,7 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) { · 56 unmodified lines
188198
189199 QVariant Text3DOverlay::getProperty(const QString& property) {
190200 if (property == "text") {
191− return _text;
201+ return getText();
192202 }
193203 if (property == "textAlpha") {
194204 return _textAlpha;
@@ -231,7 +241,7 @@ QSizeF Text3DOverlay::textSize(const QString& text) const { · 36 unmodified lines
231241 return QSizeF(extents.x, extents.y) * pointToWorldScale;
232242 }
233243
234−bool Text3DOverlay::findRayIntersection(const glm::vec3 &origin, const glm::vec3 &direction, float &distance,
244+bool Text3DOverlay::findRayIntersection(const glm::vec3 &origin, const glm::vec3 &direction, float &distance,
235245 BoxFace &face, glm::vec3& surfaceNormal) {
236246 Transform transform = getTransform();
237247 applyTransformTo(transform, true);
interface/src/ui/overlays/Text3DOverlay.h+7−6
@@ -12,14 +12,14 @@ · 11 unmodified lines
1212 #define hifi_Text3DOverlay_h
1313
1414 #include <QString>
15−
15+#include <QtCore/QMutex>
1616 #include "Billboard3DOverlay.h"
1717
1818 class TextRenderer3D;
1919
2020 class Text3DOverlay : public Billboard3DOverlay {
2121 Q_OBJECT
22−
22+
2323 public:
2424 static QString const TYPE;
2525 virtual QString getType() const override { return TYPE; }
@@ -34,7 +34,7 @@ public: · 8 unmodified lines
3434 virtual const render::ShapeKey getShapeKey() override;
3535
3636 // getters
37− const QString& getText() const { return _text; }
37+ const QString getText() const;
3838 float getLineHeight() const { return _lineHeight; }
3939 float getLeftMargin() const { return _leftMargin; }
4040 float getTopMargin() const { return _topMargin; }
@@ -45,7 +45,7 @@ public: · 4 unmodified lines
4545 float getBackgroundAlpha() { return getAlpha(); }
4646
4747 // setters
48− void setText(const QString& text) { _text = text; }
48+ void setText(const QString& text);
4949 void setTextAlpha(float alpha) { _textAlpha = alpha; }
5050 void setLineHeight(float value) { _lineHeight = value; }
5151 void setLeftMargin(float margin) { _leftMargin = margin; }
@@ -58,15 +58,16 @@ public: · 6 unmodified lines
5858
5959 QSizeF textSize(const QString& test) const; // Meters
6060
61− virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
61+ virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
6262 BoxFace& face, glm::vec3& surfaceNormal) override;
6363
6464 virtual Text3DOverlay* createClone() const override;
6565
6666 private:
6767 TextRenderer3D* _textRenderer = nullptr;
68−
68+
6969 QString _text;
70+ mutable QMutex _mutex; // used to make get/setText threadsafe, mutable so can be used in const functions
7071 xColor _backgroundColor = xColor { 0, 0, 0 };
7172 float _textAlpha { 1.0f };
7273 float _lineHeight { 1.0f };
interface/src/ui/overlays/Web3DOverlay.cpp+35−33
@@ -134,7 +134,7 @@ Web3DOverlay::~Web3DOverlay() { · 133 unmodified lines
134134 void Web3DOverlay::update(float deltatime) {
135135 if (_webSurface) {
136136 // update globalPosition
137− _webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
137+ _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
138138 }
139139 }
140140
@@ -162,57 +162,56 @@ void Web3DOverlay::loadSourceURL() { · 21 unmodified lines
162162 _webSurface->resume();
163163 _webSurface->getRootItem()->setProperty("url", _url);
164164 _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
165− _webSurface->getRootContext()->setContextProperty("ApplicationInterface", qApp);
166165
167166 } else {
168167 _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath()));
169168 _webSurface->load(_url, [&](QQmlContext* context, QObject* obj) {});
170169 _webSurface->resume();
171170
172− _webSurface->getRootContext()->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
173− _webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
174− _webSurface->getRootContext()->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
175− _webSurface->getRootContext()->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
176− _webSurface->getRootContext()->setContextProperty("Vec3", new Vec3());
177− _webSurface->getRootContext()->setContextProperty("Quat", new Quat());
178− _webSurface->getRootContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
179− _webSurface->getRootContext()->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
180− _webSurface->getRootContext()->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
171+ _webSurface->getSurfaceContext()->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
172+ _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
173+ _webSurface->getSurfaceContext()->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
174+ _webSurface->getSurfaceContext()->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
175+ _webSurface->getSurfaceContext()->setContextProperty("Vec3", new Vec3());
176+ _webSurface->getSurfaceContext()->setContextProperty("Quat", new Quat());
177+ _webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
178+ _webSurface->getSurfaceContext()->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
179+ _webSurface->getSurfaceContext()->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
181180
182181 if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
183182 auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
184183 auto flags = tabletScriptingInterface->getFlags();
185− _webSurface->getRootContext()->setContextProperty("offscreenFlags", flags);
186− _webSurface->getRootContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
187− _webSurface->getRootContext()->setContextProperty("Account", AccountScriptingInterface::getInstance());
188− _webSurface->getRootContext()->setContextProperty("Audio", DependencyManager::get<AudioScriptingInterface>().data());
189− _webSurface->getRootContext()->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
190− _webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
191− _webSurface->getRootContext()->setContextProperty("fileDialogHelper", new FileDialogHelper());
192− _webSurface->getRootContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
193− _webSurface->getRootContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
194− _webSurface->getRootContext()->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
195− _webSurface->getRootContext()->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
196− _webSurface->getRootContext()->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
197− _webSurface->getRootContext()->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().data());
198− _webSurface->getRootContext()->setContextProperty("DCModel", DependencyManager::get<DomainConnectionModel>().data());
199− _webSurface->getRootContext()->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
200− _webSurface->getRootContext()->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance());
201− _webSurface->getRootContext()->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
202− _webSurface->getRootContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance());
203−
204− _webSurface->getRootContext()->setContextProperty("pathToFonts", "../../");
184+ _webSurface->getSurfaceContext()->setContextProperty("offscreenFlags", flags);
185+ _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
186+ _webSurface->getSurfaceContext()->setContextProperty("Account", AccountScriptingInterface::getInstance());
187+ _webSurface->getSurfaceContext()->setContextProperty("Audio", DependencyManager::get<AudioScriptingInterface>().data());
188+ _webSurface->getSurfaceContext()->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
189+ _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
190+ _webSurface->getSurfaceContext()->setContextProperty("fileDialogHelper", new FileDialogHelper());
191+ _webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
192+ _webSurface->getSurfaceContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
193+ _webSurface->getSurfaceContext()->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
194+ _webSurface->getSurfaceContext()->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
195+ _webSurface->getSurfaceContext()->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
196+ _webSurface->getSurfaceContext()->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().data());
197+ _webSurface->getSurfaceContext()->setContextProperty("DCModel", DependencyManager::get<DomainConnectionModel>().data());
198+ _webSurface->getSurfaceContext()->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
199+ _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance());
200+ _webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
201+ _webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance());
202+
203+ _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../");
205204 tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());
206205
207206 // mark the TabletProxy object as cpp ownership.
208207 QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");
209− _webSurface->getRootContext()->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership);
208+ _webSurface->getSurfaceContext()->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership);
210209
211210 // Override min fps for tablet UI, for silky smooth scrolling
212211 setMaxFPS(90);
213212 }
214213 }
215− _webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
214+ _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
216215 }
217216
218217 void Web3DOverlay::setMaxFPS(uint8_t maxFPS) {
@@ -443,6 +442,9 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { · 224 unmodified lines
443442
444443 QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
445444
445+ if (this->_pressed && event.getType() == PointerEvent::Move) {
446+ return;
447+ }
446448 // Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover.
447449 // FIXME: Scroll bar dragging is a bit unstable in the tablet (content can jump up and down at times).
448450 // This may be improved in Qt 5.8. Release notes: "Cleaned up touch and mouse event delivery".
libraries/animation/src/AnimInverseKinematics.cpp+294−23
@@ -20,6 +20,8 @@ · 19 unmodified lines
2020 #include "ElbowConstraint.h"
2121 #include "SwingTwistConstraint.h"
2222 #include "AnimationLogging.h"
23+#include "CubicHermiteSpline.h"
24+#include "AnimUtil.h"
2325
2426 AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn,
2527 const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn) :
@@ -59,7 +61,8 @@ AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimN · 33 unmodified lines
5961
6062 AnimInverseKinematics::~AnimInverseKinematics() {
6163 clearConstraints();
62− _accumulators.clear();
64+ _rotationAccumulators.clear();
65+ _translationAccumulators.clear();
6366 _targetVarVec.clear();
6467 }
6568
@@ -72,10 +75,12 @@ void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) { · 6 unmodified lines
7275 assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size())));
7376 if (_skeleton->getNumJoints() == (int)poses.size()) {
7477 _relativePoses = poses;
75− _accumulators.resize(_relativePoses.size());
78+ _rotationAccumulators.resize(_relativePoses.size());
79+ _translationAccumulators.resize(_relativePoses.size());
7680 } else {
7781 _relativePoses.clear();
78− _accumulators.clear();
82+ _rotationAccumulators.clear();
83+ _translationAccumulators.clear();
7984 }
8085 }
8186
@@ -175,14 +180,17 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: · 93 unmodified lines
175180 }
176181 }
177182
178−void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets) {
183+void AnimInverseKinematics::solve(const AnimContext& context, const std::vector<IKTarget>& targets) {
179184 // compute absolute poses that correspond to relative target poses
180185 AnimPoseVec absolutePoses;
181186 absolutePoses.resize(_relativePoses.size());
182187 computeAbsolutePoses(absolutePoses);
183188
184189 // clear the accumulators before we start the IK solver
185− for (auto& accumulator: _accumulators) {
190+ for (auto& accumulator : _rotationAccumulators) {
191+ accumulator.clearAndClean();
192+ }
193+ for (auto& accumulator : _translationAccumulators) {
186194 accumulator.clearAndClean();
187195 }
188196
@@ -197,14 +205,22 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& · 8 unmodified lines
197205
198206 // solve all targets
199207 for (auto& target: targets) {
200− solveTargetWithCCD(context, target, absolutePoses, debug);
208+ if (target.getType() == IKTarget::Type::Spline) {
209+ solveTargetWithSpline(context, target, absolutePoses, debug);
210+ } else {
211+ solveTargetWithCCD(context, target, absolutePoses, debug);
212+ }
201213 }
202214
203215 // harvest accumulated rotations and apply the average
204216 for (int i = 0; i < (int)_relativePoses.size(); ++i) {
205− if (_accumulators[i].size() > 0) {
206− _relativePoses[i].rot() = _accumulators[i].getAverage();
207− _accumulators[i].clear();
217+ if (_rotationAccumulators[i].size() > 0) {
218+ _relativePoses[i].rot() = _rotationAccumulators[i].getAverage();
219+ _rotationAccumulators[i].clear();
220+ }
221+ if (_translationAccumulators[i].size() > 0) {
222+ _relativePoses[i].trans() = _translationAccumulators[i].getAverage();
223+ _translationAccumulators[i].clear();
208224 }
209225 }
210226
@@ -236,7 +252,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& · 25 unmodified lines
236252 int parentIndex = _skeleton->getParentIndex(tipIndex);
237253
238254 // update rotationOnly targets that don't lie on the ik chain of other ik targets.
239− if (parentIndex != -1 && !_accumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) {
255+ if (parentIndex != -1 && !_rotationAccumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) {
240256 const glm::quat& targetRotation = target.getRotation();
241257 // compute tip's new parent-relative rotation
242258 // Q = Qp * q --> q' = Qp^ * Q
@@ -311,10 +327,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const · 68 unmodified lines
311327 }
312328
313329 // store the relative rotation change in the accumulator
314− _accumulators[tipIndex].add(tipRelativeRotation, target.getWeight());
330+ _rotationAccumulators[tipIndex].add(tipRelativeRotation, target.getWeight());
331+
332+ glm::vec3 tipRelativeTranslation = _relativePoses[target.getIndex()].trans();
333+ _translationAccumulators[tipIndex].add(tipRelativeTranslation);
315334
316335 if (debug) {
317− debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, constrained);
336+ debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, tipRelativeTranslation, constrained);
318337 }
319338 }
320339
@@ -422,10 +441,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const · 101 unmodified lines
422441 }
423442
424443 // store the relative rotation change in the accumulator
425− _accumulators[pivotIndex].add(newRot, target.getWeight());
444+ _rotationAccumulators[pivotIndex].add(newRot, target.getWeight());
445+
446+ glm::vec3 newTrans = _relativePoses[pivotIndex].trans();
447+ _translationAccumulators[pivotIndex].add(newTrans);
426448
427449 if (debug) {
428− debugJointMap[pivotIndex] = DebugJoint(newRot, constrained);
450+ debugJointMap[pivotIndex] = DebugJoint(newRot, newTrans, constrained);
429451 }
430452
431453 // keep track of tip's new transform as we descend towards root
@@ -444,6 +466,187 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const · 12 unmodified lines
444466 }
445467 }
446468
469+static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) {
470+ float linearDistance = glm::length(basePose.trans() - tipPose.trans());
471+ glm::vec3 p0 = basePose.trans();
472+ glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y);
473+ glm::vec3 p1 = tipPose.trans();
474+ glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y);
475+
476+ return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1);
477+}
478+
479+// pre-compute information about each joint influeced by this spline IK target.
480+void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) {
481+ std::vector<SplineJointInfo> splineJointInfoVec;
482+
483+ // build spline between the default poses.
484+ AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex());
485+ AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex);
486+
487+ CubicHermiteSplineFunctorWithArcLength spline;
488+ if (target.getIndex() == _headIndex) {
489+ // set gain factors so that more curvature occurs near the tip of the spline.
490+ const float HIPS_GAIN = 0.5f;
491+ const float HEAD_GAIN = 1.0f;
492+ spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
493+ } else {
494+ spline = computeSplineFromTipAndBase(tipPose, basePose);
495+ }
496+
497+ // measure the total arc length along the spline
498+ float totalArcLength = spline.arcLength(1.0f);
499+
500+ glm::vec3 baseToTip = tipPose.trans() - basePose.trans();
501+ float baseToTipLength = glm::length(baseToTip);
502+ glm::vec3 baseToTipNormal = baseToTip / baseToTipLength;
503+
504+ int index = target.getIndex();
505+ int endIndex = _skeleton->getParentIndex(_hipsIndex);
506+ while (index != endIndex) {
507+ AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index);
508+
509+ float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength;
510+
511+ // compute offset from spline to the default pose.
512+ float t = spline.arcLengthInverse(ratio * totalArcLength);
513+
514+ // compute the rotation by using the derivative of the spline as the y-axis, and the defaultPose x-axis
515+ glm::vec3 y = glm::normalize(spline.d(t));
516+ glm::vec3 x = defaultPose.rot() * Vectors::UNIT_X;
517+ glm::vec3 u, v, w;
518+ generateBasisVectors(y, x, v, u, w);
519+ glm::mat3 m(u, v, glm::cross(u, v));
520+ glm::quat rot = glm::normalize(glm::quat_cast(m));
521+
522+ AnimPose pose(glm::vec3(1.0f), rot, spline(t));
523+ AnimPose offsetPose = pose.inverse() * defaultPose;
524+
525+ SplineJointInfo splineJointInfo = { index, ratio, offsetPose };
526+ splineJointInfoVec.push_back(splineJointInfo);
527+ index = _skeleton->getParentIndex(index);
528+ }
529+
530+ _splineJointInfoMap[target.getIndex()] = splineJointInfoVec;
531+}
532+
533+const std::vector<AnimInverseKinematics::SplineJointInfo>* AnimInverseKinematics::findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) {
534+ // find or create splineJointInfo for this target
535+ auto iter = _splineJointInfoMap.find(target.getIndex());
536+ if (iter != _splineJointInfoMap.end()) {
537+ return &(iter->second);
538+ } else {
539+ computeSplineJointInfosForIKTarget(context, target);
540+ auto iter = _splineJointInfoMap.find(target.getIndex());
541+ if (iter != _splineJointInfoMap.end()) {
542+ return &(iter->second);
543+ }
544+ }
545+
546+ return nullptr;
547+}
548+
549+void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) {
550+
551+ std::map<int, DebugJoint> debugJointMap;
552+
553+ const int baseIndex = _hipsIndex;
554+
555+ // build spline from tip to base
556+ AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation());
557+ AnimPose basePose = absolutePoses[baseIndex];
558+ CubicHermiteSplineFunctorWithArcLength spline;
559+ if (target.getIndex() == _headIndex) {
560+ // set gain factors so that more curvature occurs near the tip of the spline.
561+ const float HIPS_GAIN = 0.5f;
562+ const float HEAD_GAIN = 1.0f;
563+ spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
564+ } else {
565+ spline = computeSplineFromTipAndBase(tipPose, basePose);
566+ }
567+ float totalArcLength = spline.arcLength(1.0f);
568+
569+ // This prevents the rotation interpolation from rotating the wrong physical way (but correct mathematical way)
570+ // when the head is arched backwards very far.
571+ glm::quat halfRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), 0.5f));
572+ if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) {
573+ tipPose.rot() = -tipPose.rot();
574+ }
575+
576+ // find or create splineJointInfo for this target
577+ const std::vector<SplineJointInfo>* splineJointInfoVec = findOrCreateSplineJointInfo(context, target);
578+
579+ if (splineJointInfoVec && splineJointInfoVec->size() > 0) {
580+ const int baseParentIndex = _skeleton->getParentIndex(baseIndex);
581+ AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose();
582+
583+ // go thru splineJointInfoVec backwards (base to tip)
584+ for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) {
585+ const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i];
586+ float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength);
587+ glm::vec3 trans = spline(t);
588+
589+ // for head splines, preform most twist toward the tip by using ease in function. t^2
590+ float rotT = t;
591+ if (target.getIndex() == _headIndex) {
592+ rotT = t * t;
593+ }
594+ glm::quat twistRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), rotT));
595+
596+ // compute the rotation by using the derivative of the spline as the y-axis, and the twistRot x-axis
597+ glm::vec3 y = glm::normalize(spline.d(t));
598+ glm::vec3 x = twistRot * Vectors::UNIT_X;
599+ glm::vec3 u, v, w;
600+ generateBasisVectors(y, x, v, u, w);
601+ glm::mat3 m(u, v, glm::cross(u, v));
602+ glm::quat rot = glm::normalize(glm::quat_cast(m));
603+
604+ AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose;
605+
606+ // apply flex coefficent
607+ AnimPose flexedAbsPose;
608+ ::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose);
609+
610+ AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose;
611+ _rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight());
612+
613+ bool constrained = false;
614+ if (splineJointInfo.jointIndex != _hipsIndex) {
615+ // constrain the amount the spine can stretch or compress
616+ float length = glm::length(relPose.trans());
617+ const float EPSILON = 0.0001f;
618+ if (length > EPSILON) {
619+ float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans());
620+ const float STRETCH_COMPRESS_PERCENTAGE = 0.15f;
621+ const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE);
622+ const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE);
623+ if (length > MAX_LENGTH) {
624+ relPose.trans() = (relPose.trans() / length) * MAX_LENGTH;
625+ constrained = true;
626+ } else if (length < MIN_LENGTH) {
627+ relPose.trans() = (relPose.trans() / length) * MIN_LENGTH;
628+ constrained = true;
629+ }
630+ } else {
631+ relPose.trans() = glm::vec3(0.0f);
632+ }
633+ }
634+
635+ _translationAccumulators[splineJointInfo.jointIndex].add(relPose.trans(), target.getWeight());
636+
637+ if (debug) {
638+ debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), constrained);
639+ }
640+
641+ parentAbsPose = flexedAbsPose;
642+ }
643+ }
644+
645+ if (debug) {
646+ debugDrawIKChain(debugJointMap, context);
647+ }
648+}
649+
447650 //virtual
448651 const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) {
449652 // don't call this function, call overlay() instead
@@ -453,14 +656,9 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar · 3 unmodified lines
453656
454657 //virtual
455658 const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
456−
457659 // allows solutionSource to be overridden by an animVar
458660 auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource);
459661
460− if (context.getEnableDebugDrawIKConstraints()) {
461− debugDrawConstraints(context);
462− }
463−
464662 const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay
465663 if (dt > MAX_OVERLAY_DT) {
466664 dt = MAX_OVERLAY_DT;
@@ -569,7 +767,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars · 102 unmodified lines
569767
570768 {
571769 PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0);
572− solveWithCyclicCoordinateDescent(context, targets);
770+ solve(context, targets);
573771 }
574772
575773 if (_hipsTargetIndex < 0) {
@@ -579,6 +777,20 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars · 3 unmodified lines
579777 _hipsOffset = Vectors::ZERO;
580778 }
581779 }
780+
781+ if (context.getEnableDebugDrawIKConstraints()) {
782+ debugDrawConstraints(context);
783+ }
784+ }
785+
786+ if (_leftHandIndex > -1) {
787+ _uncontrolledLeftHandPose = _skeleton->getAbsolutePose(_leftHandIndex, underPoses);
788+ }
789+ if (_rightHandIndex > -1) {
790+ _uncontrolledRightHandPose = _skeleton->getAbsolutePose(_rightHandIndex, underPoses);
791+ }
792+ if (_hipsIndex > -1) {
793+ _uncontrolledHipsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses);
582794 }
583795
584796 return _relativePoses;
@@ -722,8 +934,10 @@ void AnimInverseKinematics::initConstraints() { · 137 unmodified lines
722934
723935 loadDefaultPoses(_skeleton->getRelativeBindPoses());
724936
725− // compute corresponding absolute poses
726937 int numJoints = (int)_defaultRelativePoses.size();
938+
939+ /* KEEP THIS CODE for future experimentation
940+ // compute corresponding absolute poses
727941 AnimPoseVec absolutePoses;
728942 absolutePoses.resize(numJoints);
729943 for (int i = 0; i < numJoints; ++i) {
@@ -734,6 +948,7 @@ void AnimInverseKinematics::initConstraints() { · 4 unmodified lines
734948 absolutePoses[i] = absolutePoses[parentIndex] * _defaultRelativePoses[i];
735949 }
736950 }
951+ */
737952
738953 clearConstraints();
739954 for (int i = 0; i < numJoints; ++i) {
@@ -1045,7 +1260,10 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele · 305 unmodified lines
10451260
10461261 _maxTargetIndex = -1;
10471262
1048− for (auto& accumulator: _accumulators) {
1263+ for (auto& accumulator: _rotationAccumulators) {
1264+ accumulator.clearAndClean();
1265+ }
1266+ for (auto& accumulator: _translationAccumulators) {
10491267 accumulator.clearAndClean();
10501268 }
10511269
@@ -1061,12 +1279,21 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele · 9 unmodified lines
10611279 } else {
10621280 _hipsParentIndex = -1;
10631281 }
1282+
1283+ _leftHandIndex = _skeleton->nameToJointIndex("LeftHand");
1284+ _rightHandIndex = _skeleton->nameToJointIndex("RightHand");
10641285 } else {
10651286 clearConstraints();
10661287 _headIndex = -1;
10671288 _hipsIndex = -1;
10681289 _hipsParentIndex = -1;
1290+ _leftHandIndex = -1;
1291+ _rightHandIndex = -1;
10691292 }
1293+
1294+ _uncontrolledLeftHandPose = AnimPose();
1295+ _uncontrolledRightHandPose = AnimPose();
1296+ _uncontrolledHipsPose = AnimPose();
10701297 }
10711298
10721299 static glm::vec3 sphericalToCartesian(float phi, float theta) {
@@ -1117,6 +1344,7 @@ void AnimInverseKinematics::debugDrawIKChain(std::map<int, DebugJoint>& debugJoi · 44 unmodified lines
11171344 // copy debug joint rotations into the relative poses
11181345 for (auto& debugJoint : debugJointMap) {
11191346 poses[debugJoint.first].rot() = debugJoint.second.relRot;
1347+ poses[debugJoint.first].trans() = debugJoint.second.relTrans;
11201348 }
11211349
11221350 // convert relative poses to absolute
@@ -1282,7 +1510,7 @@ void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const A · 159 unmodified lines
12821510 int numJoints = (int)_relativePoses.size();
12831511 for (int i = 0; i < numJoints; ++i) {
12841512 float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot()));
1285− if (_accumulators[i].isDirty()) {
1513+ if (_rotationAccumulators[i].isDirty()) {
12861514 // this joint is affected by IK --> blend toward the targetPoses rotation
12871515 _relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor));
12881516 } else {
@@ -1316,3 +1544,46 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s · 27 unmodified lines
13161544 break;
13171545 }
13181546 }
1547+
1548+void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const {
1549+
1550+ for (auto& target : targets) {
1551+
1552+ if (target.getType() != IKTarget::Type::Spline) {
1553+ continue;
1554+ }
1555+
1556+ const int baseIndex = _hipsIndex;
1557+
1558+ // build spline
1559+ AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation());
1560+ AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses);
1561+
1562+ CubicHermiteSplineFunctorWithArcLength spline;
1563+ if (target.getIndex() == _headIndex) {
1564+ // set gain factors so that more curvature occurs near the tip of the spline.
1565+ const float HIPS_GAIN = 0.5f;
1566+ const float HEAD_GAIN = 1.0f;
1567+ spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
1568+ } else {
1569+ spline = computeSplineFromTipAndBase(tipPose, basePose);
1570+ }
1571+ float totalArcLength = spline.arcLength(1.0f);
1572+
1573+ const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
1574+ const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f);
1575+
1576+ // draw red and white stripped spline, parameterized by arc length.
1577+ // i.e. each stripe should be the same length.
1578+ AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix());
1579+ const int NUM_SEGMENTS = 20;
1580+ const float dArcLength = totalArcLength / NUM_SEGMENTS;
1581+ float arcLength = 0.0f;
1582+ for (int i = 0; i < NUM_SEGMENTS; i++) {
1583+ float prevT = spline.arcLengthInverse(arcLength);
1584+ float nextT = spline.arcLengthInverse(arcLength + dArcLength);
1585+ DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(spline(prevT)), geomToWorldPose.xformPoint(spline(nextT)), (i % 2) == 0 ? RED : WHITE);
1586+ arcLength += dArcLength;
1587+ }
1588+ }
1589+}
libraries/animation/src/AnimInverseKinematics.h+29−3
@@ -19,6 +19,7 @@ · 18 unmodified lines
1919 #include "IKTarget.h"
2020
2121 #include "RotationAccumulator.h"
22+#include "TranslationAccumulator.h"
2223
2324 class RotationConstraint;
2425
@@ -56,23 +57,39 @@ public: · 31 unmodified lines
5657 void setSolutionSource(SolutionSource solutionSource) { _solutionSource = solutionSource; }
5758 void setSolutionSourceVar(const QString& solutionSourceVar) { _solutionSourceVar = solutionSourceVar; }
5859
60+ const AnimPose& getUncontrolledLeftHandPose() { return _uncontrolledLeftHandPose; }
61+ const AnimPose& getUncontrolledRightHandPose() { return _uncontrolledRightHandPose; }
62+ const AnimPose& getUncontrolledHipPose() { return _uncontrolledHipsPose; }
63+
5964 protected:
6065 void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
61− void solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets);
66+ void solve(const AnimContext& context, const std::vector<IKTarget>& targets);
6267 void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
68+ void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
6369 virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
6470 struct DebugJoint {
6571 DebugJoint() : relRot(), constrained(false) {}
66− DebugJoint(const glm::quat& relRotIn, bool constrainedIn) : relRot(relRotIn), constrained(constrainedIn) {}
72+ DebugJoint(const glm::quat& relRotIn, const glm::vec3& relTransIn, bool constrainedIn) : relRot(relRotIn), relTrans(relTransIn), constrained(constrainedIn) {}
6773 glm::quat relRot;
74+ glm::vec3 relTrans;
6875 bool constrained;
6976 };
7077 void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const;
7178 void debugDrawRelativePoses(const AnimContext& context) const;
7279 void debugDrawConstraints(const AnimContext& context) const;
80+ void debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const;
7381 void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose);
7482 void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor);
7583
84+ // used to pre-compute information about each joint influeced by a spline IK target.
85+ struct SplineJointInfo {
86+ int jointIndex; // joint in the skeleton that this information pertains to.
87+ float ratio; // percentage (0..1) along the spline for this joint.
88+ AnimPose offsetPose; // local offset from the spline to the joint.
89+ };
90+
91+ void computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target);
92+ const std::vector<SplineJointInfo>* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target);
7693
7794 // for AnimDebugDraw rendering
7895 virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; }
@@ -105,12 +122,15 @@ protected: · 26 unmodified lines
105122 };
106123
107124 std::map<int, RotationConstraint*> _constraints;
108− std::vector<RotationAccumulator> _accumulators;
125+ std::vector<RotationAccumulator> _rotationAccumulators;
126+ std::vector<TranslationAccumulator> _translationAccumulators;
109127 std::vector<IKTargetVar> _targetVarVec;
110128 AnimPoseVec _defaultRelativePoses; // poses of the relaxed state
111129 AnimPoseVec _relativePoses; // current relative poses
112130 AnimPoseVec _limitCenterPoses; // relative
113131
132+ std::map<int, std::vector<SplineJointInfo>> _splineJointInfoMap;
133+
114134 // experimental data for moving hips during IK
115135 glm::vec3 _hipsOffset { Vectors::ZERO };
116136 float _maxHipsOffsetLength{ FLT_MAX };
@@ -118,6 +138,8 @@ protected: · 1 unmodified line
118138 int _hipsIndex { -1 };
119139 int _hipsParentIndex { -1 };
120140 int _hipsTargetIndex { -1 };
141+ int _leftHandIndex { -1 };
142+ int _rightHandIndex { -1 };
121143
122144 // _maxTargetIndex is tracked to help optimize the recalculation of absolute poses
123145 // during the the cyclic coordinate descent algorithm
@@ -127,6 +149,10 @@ protected: · 3 unmodified lines
127149 bool _previousEnableDebugIKTargets { false };
128150 SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses };
129151 QString _solutionSourceVar;
152+
153+ AnimPose _uncontrolledLeftHandPose { AnimPose() };
154+ AnimPose _uncontrolledRightHandPose { AnimPose() };
155+ AnimPose _uncontrolledHipsPose { AnimPose() };
130156 };
131157
132158 #endif // hifi_AnimInverseKinematics_h
libraries/animation/src/IKTarget.cpp+3−0
@@ -44,6 +44,9 @@ void IKTarget::setType(int type) { · 43 unmodified lines
4444 case (int)Type::HipsRelativeRotationAndPosition:
4545 _type = Type::HipsRelativeRotationAndPosition;
4646 break;
47+ case (int)Type::Spline:
48+ _type = Type::Spline;
49+ break;
4750 default:
4851 _type = Type::Unknown;
4952 }
libraries/animation/src/IKTarget.h+1−0
@@ -21,6 +21,7 @@ public: · 20 unmodified lines
2121 RotationOnly,
2222 HmdHead,
2323 HipsRelativeRotationAndPosition,
24+ Spline,
2425 Unknown
2526 };
2627
libraries/animation/src/Rig.cpp+66−32
@@ -28,6 +28,7 @@ · 27 unmodified lines
2828 #include "AnimClip.h"
2929 #include "AnimInverseKinematics.h"
3030 #include "AnimSkeleton.h"
31+#include "AnimUtil.h"
3132 #include "IKTarget.h"
3233
3334 static bool isEqual(const glm::vec3& u, const glm::vec3& v) {
@@ -401,16 +402,6 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo · 367 unmodified lines
401402 }
402403 }
403404
404−void Rig::restoreJointRotation(int index, float fraction, float priority) {
405− // AJT: DEAD CODE?
406− ASSERT(false);
407−}
408−
409−void Rig::restoreJointTranslation(int index, float fraction, float priority) {
410− // AJT: DEAD CODE?
411− ASSERT(false);
412−}
413−
414405 bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const {
415406 if (isIndexValid(jointIndex)) {
416407 position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
@@ -1040,8 +1031,8 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { · 623 unmodified lines
10401031 _animVars.set("hipsType", (int)IKTarget::Type::Unknown);
10411032 }
10421033
1043− if (params.spine2Enabled) {
1044− _animVars.set("spine2Type", (int)IKTarget::Type::RotationAndPosition);
1034+ if (params.hipsEnabled && params.spine2Enabled) {
1035+ _animVars.set("spine2Type", (int)IKTarget::Type::Spline);
10451036 _animVars.set("spine2Position", extractTranslation(params.spine2Matrix));
10461037 _animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix));
10471038 } else {
@@ -1051,7 +1042,7 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { · 3 unmodified lines
10511042 if (params.leftArmEnabled) {
10521043 _animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition);
10531044 _animVars.set("leftArmPosition", params.leftArmPosition);
1054− _animVars.set("leftArmRotation", params.leftArmRotation);
1045+ _animVars.set("leftArmRotation", params.leftArmRotation);
10551046 } else {
10561047 _animVars.set("leftArmType", (int)IKTarget::Type::Unknown);
10571048 }
@@ -1101,9 +1092,9 @@ void Rig::updateHeadAnimVars(const HeadParameters& params) { · 43 unmodified lines
11011092 _animVars.set("headPosition", params.rigHeadPosition);
11021093 _animVars.set("headRotation", params.rigHeadOrientation);
11031094 if (params.hipsEnabled) {
1104− // Since there is an explicit hips ik target, switch the head to use the more generic RotationAndPosition IK chain type.
1105− // this will allow the spine to bend more, ensuring that it can reach the head target position.
1106− _animVars.set("headType", (int)IKTarget::Type::RotationAndPosition);
1095+ // Since there is an explicit hips ik target, switch the head to use the more flexible Spline IK chain type.
1096+ // this will allow the spine to compress/expand and bend more natrually, ensuring that it can reach the head target position.
1097+ _animVars.set("headType", (int)IKTarget::Type::Spline);
11071098 _animVars.unset("headWeight"); // use the default weight for this target.
11081099 } else {
11091100 // When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff,
@@ -1135,9 +1126,9 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm · 25 unmodified lines
11351126 }
11361127
11371128 glm::vec3 headUp = headQuat * Vectors::UNIT_Y;
1138− glm::vec3 z, y, x;
1139− generateBasisVectors(lookAtVector, headUp, z, y, x);
1140− glm::mat3 m(glm::cross(y, z), y, z);
1129+ glm::vec3 z, y, zCrossY;
1130+ generateBasisVectors(lookAtVector, headUp, z, y, zCrossY);
1131+ glm::mat3 m(-zCrossY, y, z);
11411132 glm::quat desiredQuat = glm::normalize(glm::quat_cast(m));
11421133
11431134 glm::quat deltaQuat = desiredQuat * glm::inverse(headQuat);
@@ -1172,10 +1163,10 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f · 28 unmodified lines
11721163 // TODO: add isHipsEnabled
11731164 bool bodySensorTrackingEnabled = params.isLeftFootEnabled || params.isRightFootEnabled;
11741165
1175− if (params.isLeftEnabled) {
1166+ const float RELAX_DURATION = 0.6f;
11761167
1168+ if (params.isLeftEnabled) {
11771169 glm::vec3 handPosition = params.leftPosition;
1178−
11791170 if (!bodySensorTrackingEnabled) {
11801171 // prevent the hand IK targets from intersecting the body capsule
11811172 glm::vec3 displacement;
@@ -1187,16 +1178,38 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f · 5 unmodified lines
11871178 _animVars.set("leftHandPosition", handPosition);
11881179 _animVars.set("leftHandRotation", params.leftOrientation);
11891180 _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
1181+
1182+ _isLeftHandControlled = true;
1183+ _lastLeftHandControlledPose = AnimPose(glm::vec3(1.0f), params.leftOrientation, handPosition);
11901184 } else {
1191− _animVars.unset("leftHandPosition");
1192− _animVars.unset("leftHandRotation");
1193− _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
1185+ if (_isLeftHandControlled) {
1186+ _leftHandRelaxDuration = RELAX_DURATION;
1187+ _isLeftHandControlled = false;
1188+ }
1189+
1190+ if (_leftHandRelaxDuration > 0) {
1191+ // Move hand from controlled position to non-controlled position.
1192+ _leftHandRelaxDuration = std::max(_leftHandRelaxDuration - dt, 0.0f);
1193+ auto ikNode = getAnimInverseKinematicsNode();
1194+ if (ikNode) {
1195+ float alpha = 1.0f - _leftHandRelaxDuration / RELAX_DURATION;
1196+ const AnimPose geometryToRigTransform(_geometryToRigTransform);
1197+ AnimPose uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledLeftHandPose();
1198+ AnimPose handPose;
1199+ ::blend(1, &_lastLeftHandControlledPose, &uncontrolledHandPose, alpha, &handPose);
1200+ _animVars.set("leftHandPosition", handPose.trans());
1201+ _animVars.set("leftHandRotation", handPose.rot());
1202+ _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
1203+ }
1204+ } else {
1205+ _animVars.unset("leftHandPosition");
1206+ _animVars.unset("leftHandRotation");
1207+ _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
1208+ }
11941209 }
11951210
11961211 if (params.isRightEnabled) {
1197−
11981212 glm::vec3 handPosition = params.rightPosition;
1199−
12001213 if (!bodySensorTrackingEnabled) {
12011214 // prevent the hand IK targets from intersecting the body capsule
12021215 glm::vec3 displacement;
@@ -1208,10 +1221,34 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f · 5 unmodified lines
12081221 _animVars.set("rightHandPosition", handPosition);
12091222 _animVars.set("rightHandRotation", params.rightOrientation);
12101223 _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
1224+
1225+ _isRightHandControlled = true;
1226+ _lastRightHandControlledPose = AnimPose(glm::vec3(1.0f), params.rightOrientation, handPosition);
12111227 } else {
1212− _animVars.unset("rightHandPosition");
1213− _animVars.unset("rightHandRotation");
1214− _animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
1228+ if (_isRightHandControlled) {
1229+ _rightHandRelaxDuration = RELAX_DURATION;
1230+ _isRightHandControlled = false;
1231+ }
1232+
1233+ if (_rightHandRelaxDuration > 0) {
1234+ // Move hand from controlled position to non-controlled position.
1235+ _rightHandRelaxDuration = std::max(_rightHandRelaxDuration - dt, 0.0f);
1236+ auto ikNode = getAnimInverseKinematicsNode();
1237+ if (ikNode) {
1238+ float alpha = 1.0f - _rightHandRelaxDuration / RELAX_DURATION;
1239+ const AnimPose geometryToRigTransform(_geometryToRigTransform);
1240+ AnimPose uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledRightHandPose();
1241+ AnimPose handPose;
1242+ ::blend(1, &_lastRightHandControlledPose, &uncontrolledHandPose, alpha, &handPose);
1243+ _animVars.set("rightHandPosition", handPose.trans());
1244+ _animVars.set("rightHandRotation", handPose.rot());
1245+ _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
1246+ }
1247+ } else {
1248+ _animVars.unset("rightHandPosition");
1249+ _animVars.unset("rightHandRotation");
1250+ _animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
1251+ }
12151252 }
12161253
12171254 if (params.isLeftFootEnabled) {
@@ -1233,7 +1270,6 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f · 15 unmodified lines
12331270 _animVars.unset("rightFootRotation");
12341271 _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
12351272 }
1236−
12371273 }
12381274 }
12391275
@@ -1509,5 +1545,3 @@ void Rig::computeAvatarBoundingCapsule( · 269 unmodified lines
15091545 glm::vec3 rigCenter = (geometryToRig * (0.5f * (totalExtents.maximum + totalExtents.minimum)));
15101546 localOffsetOut = rigCenter - (geometryToRig * rootPosition);
15111547 }
1512−
1513−
libraries/animation/src/Rig.h+7−4
@@ -134,10 +134,6 @@ public: · 133 unmodified lines
134134 void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority);
135135 void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority);
136136
137− // legacy
138− void restoreJointRotation(int index, float fraction, float priority);
139− void restoreJointTranslation(int index, float fraction, float priority);
140−
141137 // if translation and rotation is identity, position will be in rig space
142138 bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
143139 glm::vec3 translation, glm::quat rotation) const;
@@ -355,6 +351,13 @@ private: · 211 unmodified lines
355351 QMap<int, StateHandler> _stateHandlers;
356352 int _nextStateHandlerId { 0 };
357353 QMutex _stateMutex;
354+
355+ bool _isLeftHandControlled { false };
356+ bool _isRightHandControlled { false };
357+ float _leftHandRelaxDuration { 0.0f };
358+ float _rightHandRelaxDuration { 0.0f };
359+ AnimPose _lastLeftHandControlledPose;
360+ AnimPose _lastRightHandControlledPose;
358361 };
359362
360363 #endif /* defined(__hifi__Rig__) */
libraries/animation/src/RotationAccumulator.cpp+2−2
@@ -1,5 +1,5 @@
11 //
2−// RotationAccumulator.h
2+// RotationAccumulator.cpp
33 //
44 // Copyright 2015 High Fidelity, Inc.
55 //
@@ -27,7 +27,7 @@ void RotationAccumulator::clear() { · 21 unmodified lines
2727 _numRotations = 0;
2828 }
2929
30−void RotationAccumulator::clearAndClean() {
30+void RotationAccumulator::clearAndClean() {
3131 clear();
3232 _isDirty = false;
3333 }
libraries/animation/src/TranslationAccumulator.cpp+34−0
@@ -0,0 +1,34 @@
1+//
2+// TranslationAccumulator.cpp
3+//
4+// Copyright 2015 High Fidelity, Inc.
5+//
6+// Distributed under the Apache License, Version 2.0.
7+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
8+//
9+
10+#include "TranslationAccumulator.h"
11+
12+void TranslationAccumulator::add(const glm::vec3& translation, float weight) {
13+ _accum += weight * translation;
14+ _totalWeight += weight;
15+ _isDirty = true;
16+}
17+
18+glm::vec3 TranslationAccumulator::getAverage() {
19+ if (_totalWeight > 0.0f) {
20+ return _accum / _totalWeight;
21+ } else {
22+ return glm::vec3();
23+ }
24+}
25+
26+void TranslationAccumulator::clear() {
27+ _accum *= 0.0f;
28+ _totalWeight = 0.0f;
29+}
30+
31+void TranslationAccumulator::clearAndClean() {
32+ clear();
33+ _isDirty = false;
34+}
libraries/animation/src/TranslationAccumulator.h+42−0
@@ -0,0 +1,42 @@
1+//
2+// TranslationAccumulator.h
3+//
4+// Copyright 2017 High Fidelity, Inc.
5+//
6+// Distributed under the Apache License, Version 2.0.
7+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
8+//
9+
10+#ifndef hifi_TranslationAccumulator_h
11+#define hifi_TranslationAccumulator_h
12+
13+#include <glm/glm.hpp>
14+
15+class TranslationAccumulator {
16+public:
17+ TranslationAccumulator() : _accum(0.0f, 0.0f, 0.0f), _totalWeight(0), _isDirty(false) { }
18+
19+ int size() const { return _totalWeight > 0.0f; }
20+
21+ /// \param translation translation to add
22+ /// \param weight contribution factor of this translation to total accumulation
23+ void add(const glm::vec3& translation, float weight = 1.0f);
24+
25+ glm::vec3 getAverage();
26+
27+ /// \return true if any translation were accumulated
28+ bool isDirty() const { return _isDirty; }
29+
30+ /// \brief clear accumulated translation but don't change _isDirty
31+ void clear();
32+
33+ /// \brief clear accumulated translation and set _isDirty to false
34+ void clearAndClean();
35+
36+private:
37+ glm::vec3 _accum;
38+ float _totalWeight;
39+ bool _isDirty;
40+};
41+
42+#endif // hifi_TranslationAccumulator_h
libraries/audio-client/src/AudioClient.cpp+37−78
@@ -31,10 +31,13 @@ · 30 unmodified lines
3131 #include <VersionHelpers.h>
3232 #endif
3333
34+#include <QtConcurrent/QtConcurrent>
35+#include <QtCore/QThreadPool>
3436 #include <QtCore/QBuffer>
3537 #include <QtMultimedia/QAudioInput>
3638 #include <QtMultimedia/QAudioOutput>
3739
40+#include <ThreadHelpers.h>
3841 #include <NodeList.h>
3942 #include <plugins/CodecPlugin.h>
4043 #include <plugins/PluginManager.h>
@@ -83,52 +86,11 @@ QList<QAudioDeviceInfo> getAvailableDevices(QAudio::Mode mode) { · 42 unmodified lines
8386 return QAudioDeviceInfo::availableDevices(mode);
8487 }
8588
86−class BackgroundThread : public QThread {
87−public:
88− BackgroundThread(AudioClient* client) : QThread((QObject*)client), _client(client) {}
89− virtual void join() = 0;
90−protected:
91− AudioClient* _client;
92−};
93−
94−// background thread continuously polling device changes
95−class CheckDevicesThread : public BackgroundThread {
96−public:
97− CheckDevicesThread(AudioClient* client) : BackgroundThread(client) {}
98−
99− void join() override {
100− _shouldQuit = true;
101− std::unique_lock<std::mutex> lock(_joinMutex);
102− _joinCondition.wait(lock, [&]{ return !_isRunning; });
103− }
104−
105−protected:
106− void run() override {
107− while (!_shouldQuit) {
108− _client->checkDevices();
109−
110− const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000;
111− QThread::msleep(DEVICE_CHECK_INTERVAL_MSECS);
112− }
113− std::lock_guard<std::mutex> lock(_joinMutex);
114− _isRunning = false;
115− _joinCondition.notify_one();
116− }
117−
118−private:
119− std::atomic<bool> _shouldQuit { false };
120− bool _isRunning { true };
121− std::mutex _joinMutex;
122− std::condition_variable _joinCondition;
123−};
124−
12589 // now called from a background thread, to keep blocking operations off the audio thread
12690 void AudioClient::checkDevices() {
12791 auto inputDevices = getAvailableDevices(QAudio::AudioInput);
12892 auto outputDevices = getAvailableDevices(QAudio::AudioOutput);
12993
130− Lock lock(_deviceMutex);
131−
13294 if (inputDevices != _inputDevices) {
13395 _inputDevices.swap(inputDevices);
13496 emit devicesChanged(QAudio::AudioInput, _inputDevices);
@@ -160,21 +122,6 @@ QList<QAudioDeviceInfo> AudioClient::getAudioDevices(QAudio::Mode mode) const { · 25 unmodified lines
160122 }
161123 }
162124
163−
164−// background thread buffering local injectors
165−class LocalInjectorsThread : public BackgroundThread {
166− Q_OBJECT
167−public:
168− LocalInjectorsThread(AudioClient* client) : BackgroundThread(client) {}
169−
170− void join() override { return; }
171−
172−private slots:
173− void prepare() { _client->prepareLocalAudioInjectors(); }
174−};
175−
176−#include "AudioClient.moc"
177−
178125 static void channelUpmix(int16_t* source, int16_t* dest, int numSamples, int numExtraChannels) {
179126 for (int i = 0; i < numSamples/2; i++) {
180127
@@ -262,7 +209,10 @@ AudioClient::AudioClient() : · 81 unmodified lines
262209
263210 connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples,
264211 this, &AudioClient::processReceivedSamples, Qt::DirectConnection);
265− connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) { switchOutputToAudioDevice(outputDeviceInfo); });
212+ connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) {
213+ qCDebug(audioclient) << "got AudioClient::changeDevice signal, about to call switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]";
214+ switchOutputToAudioDevice(outputDeviceInfo);
215+ });
266216
267217 connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat);
268218
@@ -270,16 +220,15 @@ AudioClient::AudioClient() : · 1 unmodified line
270220 getAvailableDevices(QAudio::AudioInput);
271221 getAvailableDevices(QAudio::AudioOutput);
272222
223+
273224 // start a thread to detect any device changes
274− _checkDevicesThread = new CheckDevicesThread(this);
275− _checkDevicesThread->setObjectName("AudioClient CheckDevices Thread");
276− _checkDevicesThread->setPriority(QThread::LowPriority);
277− _checkDevicesThread->start();
225+ _checkDevicesTimer = new QTimer(this);
226+ connect(_checkDevicesTimer, &QTimer::timeout, [this] {
227+ QtConcurrent::run(QThreadPool::globalInstance(), [this] {
228+ checkDevices();
229+ });
230+ });
278231
279− // start a thread to process local injectors
280− _localInjectorsThread = new LocalInjectorsThread(this);
281− _localInjectorsThread->setObjectName("AudioClient LocalInjectors Thread");
282− _localInjectorsThread->start();
283232
284233 configureReverb();
285234
@@ -310,15 +259,7 @@ void AudioClient::cleanupBeforeQuit() { · 24 unmodified lines
310259
311260 stop();
312261
313− if (_checkDevicesThread) {
314− static_cast<BackgroundThread*>(_checkDevicesThread)->join();
315− delete _checkDevicesThread;
316− }
317−
318− if (_localInjectorsThread) {
319− static_cast<BackgroundThread*>(_localInjectorsThread)->join();
320− delete _localInjectorsThread;
321− }
262+ _checkDevicesTimer->stop();
322263 }
323264
324265 void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) {
@@ -478,7 +419,8 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { · 153 unmodified lines
478419 CoUninitialize();
479420 }
480421
481− qCDebug(audioclient) << "[" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]";
422+ qCDebug(audioclient) << "defaultAudioDeviceForMode mode: " << (mode == QAudio::AudioOutput ? "Output" : "Input")
423+ << " [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]";
482424
483425 return getNamedAudioDeviceForMode(mode, deviceName);
484426 #endif
@@ -654,8 +596,12 @@ void AudioClient::start() { · 169 unmodified lines
654596 }
655597
656598 void AudioClient::stop() {
599+
657600 // "switch" to invalid devices in order to shut down the state
601+ qCDebug(audioclient) << "AudioClient::stop(), about to call switchInputToAudioDevice(null)";
658602 switchInputToAudioDevice(QAudioDeviceInfo());
603+
604+ qCDebug(audioclient) << "AudioClient::stop(), about to call switchOutputToAudioDevice(null)";
659605 switchOutputToAudioDevice(QAudioDeviceInfo());
660606 }
661607
@@ -1402,10 +1348,8 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) { · 740 unmodified lines
14021348 if (!_activeLocalAudioInjectors.contains(injector)) {
14031349 qCDebug(audioclient) << "adding new injector";
14041350 _activeLocalAudioInjectors.append(injector);
1405−
14061351 // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop())
14071352 injectorBuffer->setParent(nullptr);
1408− injectorBuffer->moveToThread(_localInjectorsThread);
14091353
14101354 // update the flag
14111355 _localInjectorsAvailable.exchange(true, std::memory_order_release);
@@ -1428,6 +1372,7 @@ void AudioClient::outputFormatChanged() { · 16 unmodified lines
14281372 }
14291373
14301374 bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) {
1375+ qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << inputDeviceInfo.deviceName() << "]";
14311376 bool supportedFormat = false;
14321377
14331378 // NOTE: device start() uses the Qt internal device list
@@ -1542,6 +1487,8 @@ void AudioClient::outputNotify() { · 108 unmodified lines
15421487 }
15431488
15441489 bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) {
1490+ qCDebug(audioclient) << "AudioClient::switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]";
1491+
15451492 bool supportedFormat = false;
15461493
15471494 // NOTE: device start() uses the Qt internal device list
@@ -1678,7 +1625,11 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice · 130 unmodified lines
16781625 }
16791626
16801627 int AudioClient::setOutputBufferSize(int numFrames, bool persist) {
1628+ qCDebug(audioclient) << __FUNCTION__ << "numFrames:" << numFrames << "persist:" << persist;
1629+
16811630 numFrames = std::min(std::max(numFrames, MIN_BUFFER_FRAMES), MAX_BUFFER_FRAMES);
1631+ qCDebug(audioclient) << __FUNCTION__ << "clamped numFrames:" << numFrames << "_sessionOutputBufferSizeFrames:" << _sessionOutputBufferSizeFrames;
1632+
16821633 if (numFrames != _sessionOutputBufferSizeFrames) {
16831634 qCInfo(audioclient, "Audio output buffer set to %d frames", numFrames);
16841635 _sessionOutputBufferSizeFrames = numFrames;
@@ -1690,6 +1641,7 @@ int AudioClient::setOutputBufferSize(int numFrames, bool persist) { · 5 unmodified lines
16901641 // The buffer size can't be adjusted after QAudioOutput::start() has been called, so
16911642 // recreate the device by switching to the default.
16921643 QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput);
1644+ qCDebug(audioclient) << __FUNCTION__ << "about to send changeDevice signal outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]";
16931645 emit changeDevice(outputDeviceInfo); // On correct thread, please, as setOutputBufferSize can be called from main thread.
16941646 }
16951647 }
@@ -1817,7 +1769,9 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { · 121 unmodified lines
18171769 }
18181770
18191771 // prepare injectors for the next callback
1820− QMetaObject::invokeMethod(_audio->_localInjectorsThread, "prepare", Qt::QueuedConnection);
1772+ QtConcurrent::run(QThreadPool::globalInstance(), [this] {
1773+ _audio->prepareLocalAudioInjectors();
1774+ });
18211775
18221776 int samplesPopped = std::max(networkSamplesPopped, injectorSamplesPopped);
18231777 int framesPopped = samplesPopped / AudioConstants::STEREO;
@@ -1877,3 +1831,8 @@ void AudioClient::setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 sca · 53 unmodified lines
18771831 avatarBoundingBoxCorner = corner;
18781832 avatarBoundingBoxScale = scale;
18791833 }
1834+
1835+
1836+void AudioClient::startThread() {
1837+ moveToNewNamedThread(this, "Audio Thread", [this] { start(); });
1838+}
libraries/audio-client/src/AudioClient.h+2−2
@@ -104,6 +104,7 @@ public: · 103 unmodified lines
104104 int _unfulfilledReads;
105105 };
106106
107+ void startThread();
107108 void negotiateAudioFormat();
108109 void selectAudioFormat(const QString& selectedCodecName);
109110
@@ -386,8 +387,7 @@ private: · 276 unmodified lines
386387 RateCounter<> _silentInbound;
387388 RateCounter<> _audioInbound;
388389
389− QThread* _checkDevicesThread { nullptr };
390− QThread* _localInjectorsThread { nullptr };
390+ QTimer* _checkDevicesTimer { nullptr };
391391 };
392392
393393
libraries/audio/src/Sound.cpp+45−16
@@ -13,6 +13,8 @@ · 12 unmodified lines
1313
1414 #include <glm/glm.hpp>
1515
16+#include <QRunnable>
17+#include <QThreadPool>
1618 #include <QDataStream>
1719 #include <QtCore/QDebug>
1820 #include <QtNetwork/QNetworkRequest>
@@ -49,13 +51,43 @@ Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) : · 30 unmodified lines
4951 _isAmbisonic(isAmbisonic),
5052 _isReady(false)
5153 {
52−
5354 }
5455
5556 void Sound::downloadFinished(const QByteArray& data) {
57+ // this is a QRunnable, will delete itself after it has finished running
58+ SoundProcessor* soundProcessor = new SoundProcessor(_url, data, _isStereo, _isAmbisonic);
59+ connect(soundProcessor, &SoundProcessor::onSuccess, this, &Sound::soundProcessSuccess);
60+ connect(soundProcessor, &SoundProcessor::onError, this, &Sound::soundProcessError);
61+ QThreadPool::globalInstance()->start(soundProcessor);
62+}
63+
64+void Sound::soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration) {
65+
66+ qCDebug(audio) << "Setting ready state for sound file" << _url.toDisplayString();
67+
68+ _byteArray = data;
69+ _isStereo = stereo;
70+ _isAmbisonic = ambisonic;
71+ _duration = duration;
72+ _isReady = true;
73+ finishedLoading(true);
74+
75+ emit ready();
76+}
77+
78+void Sound::soundProcessError(int error, QString str) {
79+ qCCritical(audio) << "Failed to process sound file" << _url.toDisplayString() << "code =" << error << str;
80+ emit failed(QNetworkReply::UnknownContentError);
81+ finishedLoading(false);
82+}
83+
84+void SoundProcessor::run() {
85+
86+ qCDebug(audio) << "Processing sound file" << _url.toDisplayString();
87+
5688 // replace our byte array with the downloaded data
57− QByteArray rawAudioByteArray = QByteArray(data);
58− QString fileName = getURL().fileName().toLower();
89+ QByteArray rawAudioByteArray = QByteArray(_data);
90+ QString fileName = _url.fileName().toLower();
5991
6092 static const QString WAV_EXTENSION = ".wav";
6193 static const QString RAW_EXTENSION = ".raw";
@@ -72,31 +104,28 @@ void Sound::downloadFinished(const QByteArray& data) { · 10 unmodified lines
72104 // since it's raw the only way for us to know that is if the file was called .stereo.raw
73105 if (fileName.toLower().endsWith("stereo.raw")) {
74106 _isStereo = true;
75− qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << getURL() << "as stereo audio file.";
107+ qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << _url << "as stereo audio file.";
76108 }
77109
78110 // Process as 48khz RAW file
79111 downSample(rawAudioByteArray, 48000);
80112 } else {
81113 qCDebug(audio) << "Unknown sound file type";
114+ emit onError(300, "Failed to load sound file, reason: unknown sound file type");
115+ return;
82116 }
83117
84− finishedLoading(true);
85−
86− _isReady = true;
87− emit ready();
118+ emit onSuccess(_data, _isStereo, _isAmbisonic, _duration);
88119 }
89120
90−void Sound::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
121+void SoundProcessor::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
91122
92123 // we want to convert it to the format that the audio-mixer wants
93124 // which is signed, 16-bit, 24Khz
94125
95126 if (sampleRate == AudioConstants::SAMPLE_RATE) {
96−
97127 // no resampling needed
98− _byteArray = rawAudioByteArray;
99−
128+ _data = rawAudioByteArray;
100129 } else {
101130
102131 int numChannels = _isAmbisonic ? AudioConstants::AMBISONIC : (_isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
@@ -106,15 +135,15 @@ void Sound::downSample(const QByteArray& rawAudioByteArray, int sampleRate) { · 3 unmodified lines
106135 int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
107136 int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
108137 int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
109− _byteArray.resize(maxDestinationBytes);
138+ _data.resize(maxDestinationBytes);
110139
111140 int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
112− (int16_t*)_byteArray.data(),
141+ (int16_t*)_data.data(),
113142 numSourceFrames);
114143
115144 // truncate to actual output
116145 int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
117− _byteArray.resize(numDestinationBytes);
146+ _data.resize(numDestinationBytes);
118147 }
119148 }
120149
@@ -163,7 +192,7 @@ struct WAVEFormat { · 42 unmodified lines
163192 };
164193
165194 // returns wavfile sample rate, used for resampling
166−int Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
195+int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
167196
168197 // Create a data stream to analyze the data
169198 QDataStream waveStream(const_cast<QByteArray *>(&inputAudioByteArray), QIODevice::ReadOnly);
libraries/audio/src/Sound.h+30−3
@@ -12,6 +12,7 @@ · 11 unmodified lines
1212 #ifndef hifi_Sound_h
1313 #define hifi_Sound_h
1414
15+#include <QRunnable>
1516 #include <QtCore/QObject>
1617 #include <QtNetwork/QNetworkReply>
1718 #include <QtScript/qscriptengine.h>
@@ -28,12 +29,15 @@ public: · 10 unmodified lines
2829 bool isAmbisonic() const { return _isAmbisonic; }
2930 bool isReady() const { return _isReady; }
3031 float getDuration() const { return _duration; }
31−
3232
3333 const QByteArray& getByteArray() const { return _byteArray; }
3434
3535 signals:
3636 void ready();
37+
38+protected slots:
39+ void soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration);
40+ void soundProcessError(int error, QString str);
3741
3842 private:
3943 QByteArray _byteArray;
@@ -42,10 +46,33 @@ private: · 2 unmodified lines
4246 bool _isReady;
4347 float _duration; // In seconds
4448
49+ virtual void downloadFinished(const QByteArray& data) override;
50+};
51+
52+class SoundProcessor : public QObject, public QRunnable {
53+ Q_OBJECT
54+
55+public:
56+ SoundProcessor(const QUrl& url, const QByteArray& data, bool stereo, bool ambisonic)
57+ : _url(url), _data(data), _isStereo(stereo), _isAmbisonic(ambisonic)
58+ {
59+ }
60+
61+ virtual void run() override;
62+
4563 void downSample(const QByteArray& rawAudioByteArray, int sampleRate);
4664 int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
47−
48− virtual void downloadFinished(const QByteArray& data) override;
65+
66+signals:
67+ void onSuccess(QByteArray data, bool stereo, bool ambisonic, float duration);
68+ void onError(int error, QString str);
69+
70+private:
71+ QUrl _url;
72+ QByteArray _data;
73+ bool _isStereo;
74+ bool _isAmbisonic;
75+ float _duration;
4976 };
5077
5178 typedef QSharedPointer<Sound> SharedSoundPointer;
libraries/avatars-renderer/src/avatars-renderer/Head.cpp+0−12
@@ -304,18 +304,6 @@ glm::quat Head::getEyeRotation(const glm::vec3& eyePosition) const { · 303 unmodified lines
304304 return rotationBetween(orientation * IDENTITY_FORWARD, lookAtDelta + glm::length(lookAtDelta) * _saccade) * orientation;
305305 }
306306
307−void Head::setFinalPitch(float finalPitch) {
308− _deltaPitch = glm::clamp(finalPitch, MIN_HEAD_PITCH, MAX_HEAD_PITCH) - _basePitch;
309−}
310−
311−void Head::setFinalYaw(float finalYaw) {
312− _deltaYaw = glm::clamp(finalYaw, MIN_HEAD_YAW, MAX_HEAD_YAW) - _baseYaw;
313−}
314−
315−void Head::setFinalRoll(float finalRoll) {
316− _deltaRoll = glm::clamp(finalRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL) - _baseRoll;
317−}
318−
319307 float Head::getFinalYaw() const {
320308 return glm::clamp(_baseYaw + _deltaYaw, MIN_HEAD_YAW, MAX_HEAD_YAW);
321309 }
libraries/avatars-renderer/src/avatars-renderer/Head.h+0−3
@@ -71,9 +71,6 @@ public: · 70 unmodified lines
7171 void setDeltaRoll(float roll) { _deltaRoll = roll; }
7272 float getDeltaRoll() const { return _deltaRoll; }
7373
74− virtual void setFinalYaw(float finalYaw) override;
75− virtual void setFinalPitch(float finalPitch) override;
76− virtual void setFinalRoll(float finalRoll) override;
7774 virtual float getFinalPitch() const override;
7875 virtual float getFinalYaw() const override;
7976 virtual float getFinalRoll() const override;
libraries/avatars/src/AvatarData.cpp+4−1
@@ -850,7 +850,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { · 849 unmodified lines
850850 glm::quat sensorToWorldQuat;
851851 unpackOrientationQuatFromSixBytes(data->sensorToWorldQuat, sensorToWorldQuat);
852852 float sensorToWorldScale;
853− unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&data->sensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
853+ // Grab a local copy of sensorToWorldScale to be able to use the unpack function with a pointer on it,
854+ // a direct pointer on the struct attribute triggers warnings because of potential misalignement.
855+ auto srcSensorToWorldScale = data->sensorToWorldScale;
856+ unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&srcSensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
854857 glm::vec3 sensorToWorldTrans(data->sensorToWorldTrans[0], data->sensorToWorldTrans[1], data->sensorToWorldTrans[2]);
855858 glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans);
856859 if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) {
libraries/avatars/src/HeadData.h+0−3
@@ -45,9 +45,6 @@ public: · 44 unmodified lines
4545 float getBaseRoll() const { return _baseRoll; }
4646 void setBaseRoll(float roll) { _baseRoll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); }
4747
48− virtual void setFinalYaw(float finalYaw) { _baseYaw = finalYaw; }
49− virtual void setFinalPitch(float finalPitch) { _basePitch = finalPitch; }
50− virtual void setFinalRoll(float finalRoll) { _baseRoll = finalRoll; }
5148 virtual float getFinalYaw() const { return _baseYaw; }
5249 virtual float getFinalPitch() const { return _basePitch; }
5350 virtual float getFinalRoll() const { return _baseRoll; }
libraries/controllers/src/controllers/Actions.h+2−2
@@ -42,8 +42,6 @@ enum class Action { · 41 unmodified lines
4242
4343 LEFT_HAND = NUM_COMBINED_AXES,
4444 RIGHT_HAND,
45− LEFT_ARM,
46− RIGHT_ARM,
4745 LEFT_FOOT,
4846 RIGHT_FOOT,
4947 HIPS,
@@ -103,6 +101,8 @@ enum class Action { · 53 unmodified lines
103101 // Bisected aliases for TRANSLATE_CAMERA_Z
104102 BOOM_IN,
105103 BOOM_OUT,
104+ LEFT_ARM,
105+ RIGHT_ARM,
106106
107107
108108 NUM_ACTIONS,
libraries/controllers/src/controllers/InputRecorder.cpp+77−46
@@ -9,7 +9,6 @@ · 8 unmodified lines
99 #include "InputRecorder.h"
1010
1111 #include <QJsonArray>
12−#include <QJsonObject>
1312 #include <QJsonDocument>
1413 #include <QFile>
1514 #include <QDir>
@@ -19,13 +18,17 @@ · 3 unmodified lines
1918 #include <QByteArray>
2019 #include <QStandardPaths>
2120 #include <PathUtils.h>
21+#include <QUrl>
22+#include <Gzip.h>
2223
2324 #include <BuildInfo.h>
2425 #include <GLMHelpers.h>
26+#include <DependencyManager.h>
27+#include "UserInputMapper.h"
2528
2629 QString SAVE_DIRECTORY = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + BuildInfo::MODIFIED_ORGANIZATION + "/" + BuildInfo::INTERFACE_NAME + "/hifi-input-recordings/";
2730 QString FILE_PREFIX_NAME = "input-recording-";
28−QString COMPRESS_EXTENSION = "json.gz";
31+QString COMPRESS_EXTENSION = ".json.gz";
2932 namespace controller {
3033
3134 QJsonObject poseToJsonObject(const Pose pose) {
@@ -91,23 +94,27 @@ namespace controller { · 59 unmodified lines
9194 }
9295
9396
94− void exportToFile(QJsonObject& object) {
97+ void exportToFile(const QJsonObject& object, const QString& fileName) {
9598 if (!QDir(SAVE_DIRECTORY).exists()) {
9699 QDir().mkdir(SAVE_DIRECTORY);
97100 }
98−
99− QString timeStamp = QDateTime::currentDateTime().toString(Qt::ISODate);
100− timeStamp.replace(":", "-");
101− QString fileName = SAVE_DIRECTORY + FILE_PREFIX_NAME + timeStamp + COMPRESS_EXTENSION;
102− qDebug() << fileName;
101+
103102 QFile saveFile (fileName);
104103 if (!saveFile.open(QIODevice::WriteOnly)) {
105104 qWarning() << "could not open file: " << fileName;
106105 return;
107106 }
108107 QJsonDocument saveData(object);
109− QByteArray compressedData = qCompress(saveData.toJson(QJsonDocument::Compact));
110− saveFile.write(compressedData);
108+ QByteArray jsonData = saveData.toJson(QJsonDocument::Indented);
109+ QByteArray jsonDataForFile;
110+
111+ if (!gzip(jsonData, jsonDataForFile, -1)) {
112+ qCritical("unable to gzip while saving to json.");
113+ return;
114+ }
115+
116+ saveFile.write(jsonDataForFile);
117+ saveFile.close();
111118 }
112119
113120 QJsonObject openFile(const QString& file, bool& status) {
@@ -118,10 +125,19 @@ namespace controller { · 4 unmodified lines
118125 status = false;
119126 return object;
120127 }
121− QByteArray compressedData = qUncompress(openFile.readAll());
122− QJsonDocument jsonDoc = QJsonDocument::fromJson(compressedData);
128+ QByteArray compressedData = openFile.readAll();
129+ QByteArray jsonData;
130+
131+ if (!gunzip(compressedData, jsonData)) {
132+ qCritical() << "json file not in gzip format: " << file;
133+ status = false;
134+ return object;
135+ }
136+
137+ QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
123138 object = jsonDoc.object();
124139 status = true;
140+ openFile.close();
125141 return object;
126142 }
127143
@@ -146,31 +162,47 @@ namespace controller { · 18 unmodified lines
146162 _actionStateList.clear();
147163 }
148164
149− void InputRecorder::saveRecording() {
165+ QJsonObject InputRecorder::recordDataToJson() {
150166 QJsonObject data;
151167 data["frameCount"] = _framesRecorded;
152−
168+ data["version"] = "0.0";
169+
153170 QJsonArray actionArrayList;
154171 QJsonArray poseArrayList;
155172 for(const ActionStates actionState: _actionStateList) {
156173 QJsonArray actionArray;
157− for (const float value: actionState) {
158− actionArray.append(value);
174+ for (const auto action: actionState) {
175+ QJsonObject actionJson;
176+ actionJson["name"] = action.first;
177+ actionJson["value"] = action.second;
178+ actionArray.append(actionJson);
159179 }
160180 actionArrayList.append(actionArray);
161181 }
162182
163183 for (const PoseStates poseState: _poseStateList) {
164184 QJsonArray poseArray;
165− for (const Pose pose: poseState) {
166− poseArray.append(poseToJsonObject(pose));
185+ for (const auto pose: poseState) {
186+ QJsonObject poseJson;
187+ poseJson["name"] = pose.first;
188+ poseJson["pose"] = poseToJsonObject(pose.second);
189+ poseArray.append(poseJson);
167190 }
168191 poseArrayList.append(poseArray);
169192 }
170193
171194 data["actionList"] = actionArrayList;
172195 data["poseList"] = poseArrayList;
173− exportToFile(data);
196+
197+ return data;
198+ }
199+
200+ void InputRecorder::saveRecording() {
201+ QJsonObject jsonData = recordDataToJson();
202+ QString timeStamp = QDateTime::currentDateTime().toString(Qt::ISODate);
203+ timeStamp.replace(":", "-");
204+ QString fileName = SAVE_DIRECTORY + FILE_PREFIX_NAME + timeStamp + COMPRESS_EXTENSION;
205+ exportToFile(jsonData, fileName);
174206 }
175207
176208 void InputRecorder::loadRecording(const QString& path) {
@@ -181,17 +213,20 @@ namespace controller { · 4 unmodified lines
181213 resetFrame();
182214 _poseStateList.clear();
183215 _actionStateList.clear();
184− QString filePath = path;
185− filePath.remove(0,8);
216+ QUrl urlPath(path);
217+ QString filePath = urlPath.toLocalFile();
186218 QFileInfo info(filePath);
187219 QString extension = info.suffix();
220+
188221 if (extension != "gz") {
189222 qWarning() << "can not load file with exentsion of " << extension;
190223 return;
191224 }
225+
192226 bool success = false;
193− QJsonObject data = openFile(info.absoluteFilePath(), success);
194− if (success) {
227+ QJsonObject data = openFile(filePath, success);
228+ auto keyValue = data.find("version");
229+ if (success && keyValue != data.end()) {
195230 _framesRecorded = data["frameCount"].toInt();
196231 QJsonArray actionArrayList = data["actionList"].toArray();
197232 QJsonArray poseArrayList = data["poseList"].toArray();
@@ -199,30 +234,31 @@ namespace controller { · 1 unmodified line
199234 for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) {
200235 QJsonArray actionState = actionArrayList[actionIndex].toArray();
201236 for (int index = 0; index < actionState.size(); index++) {
202− _currentFrameActions[index] = actionState[index].toDouble();
237+ QJsonObject actionObject = actionState[index].toObject();
238+ _currentFrameActions[actionObject["name"].toString()] = actionObject["value"].toDouble();
203239 }
204240 _actionStateList.push_back(_currentFrameActions);
205− _currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS));
241+ _currentFrameActions.clear();
206242 }
207243
208244 for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) {
209245 QJsonArray poseState = poseArrayList[poseIndex].toArray();
210246 for (int index = 0; index < poseState.size(); index++) {
211− _currentFramePoses[index] = jsonObjectToPose(poseState[index].toObject());
247+ QJsonObject poseObject = poseState[index].toObject();
248+ _currentFramePoses[poseObject["name"].toString()] = jsonObjectToPose(poseObject["pose"].toObject());
212249 }
213250 _poseStateList.push_back(_currentFramePoses);
214− _currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS));
251+ _currentFramePoses.clear();
215252 }
216− }
217−
253+ }
218254 _loading = false;
219255 }
220−
256+
221257 void InputRecorder::stopRecording() {
222258 _recording = false;
223259 _framesRecorded = (int)_actionStateList.size();
224260 }
225−
261+
226262 void InputRecorder::startPlayback() {
227263 _playback = true;
228264 _recording = false;
@@ -234,41 +270,36 @@ namespace controller { · 5 unmodified lines
234270 _playCount = 0;
235271 }
236272
237− void InputRecorder::setActionState(controller::Action action, float value) {
273+ void InputRecorder::setActionState(const QString& action, float value) {
238274 if (_recording) {
239− _currentFrameActions[toInt(action)] += value;
275+ _currentFrameActions[action] += value;
240276 }
241277 }
242278
243− void InputRecorder::setActionState(controller::Action action, const controller::Pose pose) {
279+ void InputRecorder::setActionState(const QString& action, const controller::Pose& pose) {
244280 if (_recording) {
245− _currentFramePoses[toInt(action)] = pose;
281+ _currentFramePoses[action] = pose;
246282 }
247283 }
248284
249285 void InputRecorder::resetFrame() {
250286 if (_recording) {
251− for(auto& channel : _currentFramePoses) {
252− channel = Pose();
253− }
254−
255− for(auto& channel : _currentFrameActions) {
256− channel = 0.0f;
257− }
287+ _currentFramePoses.clear();
288+ _currentFrameActions.clear();
258289 }
259290 }
260291
261− float InputRecorder::getActionState(controller::Action action) {
292+ float InputRecorder::getActionState(const QString& action) {
262293 if (_actionStateList.size() > 0 ) {
263− return _actionStateList[_playCount][toInt(action)];
294+ return _actionStateList[_playCount][action];
264295 }
265296
266297 return 0.0f;
267298 }
268299
269− controller::Pose InputRecorder::getPoseState(controller::Action action) {
300+ controller::Pose InputRecorder::getPoseState(const QString& action) {
270301 if (_poseStateList.size() > 0) {
271− return _poseStateList[_playCount][toInt(action)];
302+ return _poseStateList[_playCount][action];
272303 }
273304
274305 return Pose();
libraries/controllers/src/controllers/InputRecorder.h+11−8
@@ -12,8 +12,10 @@ · 11 unmodified lines
1212 #include <mutex>
1313 #include <atomic>
1414 #include <vector>
15+#include <map>
1516
1617 #include <QString>
18+#include <QJsonObject>
1719
1820 #include "Pose.h"
1921 #include "Actions.h"
@@ -21,8 +23,8 @@ · 1 unmodified line
2123 namespace controller {
2224 class InputRecorder {
2325 public:
24− using PoseStates = std::vector<Pose>;
25− using ActionStates = std::vector<float>;
26+ using PoseStates = std::map<QString, Pose>;
27+ using ActionStates = std::map<QString, float>;
2628
2729 InputRecorder();
2830 ~InputRecorder();
@@ -40,20 +42,21 @@ namespace controller { · 11 unmodified lines
4042 void resetFrame();
4143 bool isRecording() { return _recording; }
4244 bool isPlayingback() { return (_playback && !_loading); }
43− void setActionState(controller::Action action, float value);
44− void setActionState(controller::Action action, const controller::Pose pose);
45− float getActionState(controller::Action action);
46− controller::Pose getPoseState(controller::Action action);
45+ void setActionState(const QString& action, float value);
46+ void setActionState(const QString& action, const controller::Pose& pose);
47+ float getActionState(const QString& action);
48+ controller::Pose getPoseState(const QString& action);
4749 QString getSaveDirectory();
4850 void frameTick();
4951 private:
52+ QJsonObject recordDataToJson();
5053 bool _recording { false };
5154 bool _playback { false };
5255 bool _loading { false };
5356 std::vector<PoseStates> _poseStateList = std::vector<PoseStates>();
5457 std::vector<ActionStates> _actionStateList = std::vector<ActionStates>();
55− PoseStates _currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS));
56− ActionStates _currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS));
58+ PoseStates _currentFramePoses;
59+ ActionStates _currentFrameActions;
5760
5861 int _framesRecorded { 0 };
5962 int _playCount { 0 };
libraries/controllers/src/controllers/Pose.h+1−0
@@ -39,6 +39,7 @@ namespace controller { · 38 unmodified lines
3939 quat getRotation() const { return rotation; }
4040 vec3 getVelocity() const { return velocity; }
4141 vec3 getAngularVelocity() const { return angularVelocity; }
42+ mat4 getMatrix() const { return createMatFromQuatAndPos(rotation, translation); }
4243
4344 Pose transform(const glm::mat4& mat) const;
4445 Pose postTransform(const glm::mat4& mat) const;
libraries/controllers/src/controllers/UserInputMapper.cpp+10−0
@@ -329,6 +329,16 @@ QString UserInputMapper::getActionName(Action action) const { · 328 unmodified lines
329329 return QString();
330330 }
331331
332+QString UserInputMapper::getStandardPoseName(uint16_t pose) {
333+ Locker locker(_lock);
334+ for (auto posePair : getStandardInputs()) {
335+ if (posePair.first.channel == pose && posePair.first.getType() == ChannelType::POSE) {
336+ return posePair.second;
337+ }
338+ }
339+ return QString();
340+}
341+
332342 QVector<QString> UserInputMapper::getActionNames() const {
333343 Locker locker(_lock);
334344 QVector<QString> result;
libraries/controllers/src/controllers/UserInputMapper.h+1−0
@@ -80,6 +80,7 @@ namespace controller { · 79 unmodified lines
8080
8181 QVector<Action> getAllActions() const;
8282 QString getActionName(Action action) const;
83+ QString getStandardPoseName(uint16_t pose);
8384 float getActionState(Action action) const { return _actionStates[toInt(action)]; }
8485 Pose getPoseState(Action action) const;
8586 int findAction(const QString& actionName) const;
libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp+7−8
@@ -17,31 +17,30 @@ using namespace controller; · 16 unmodified lines
1717
1818 void ActionEndpoint::apply(float newValue, const Pointer& source) {
1919 InputRecorder* inputRecorder = InputRecorder::getInstance();
20+ auto userInputMapper = DependencyManager::get<UserInputMapper>();
21+ QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
2022 if(inputRecorder->isPlayingback()) {
21− newValue = inputRecorder->getActionState(Action(_input.getChannel()));
23+ newValue = inputRecorder->getActionState(actionName);
2224 }
2325
2426 _currentValue += newValue;
2527 if (_input != Input::INVALID_INPUT) {
26− auto userInputMapper = DependencyManager::get<UserInputMapper>();
2728 userInputMapper->deltaActionState(Action(_input.getChannel()), newValue);
2829 }
29− inputRecorder->setActionState(Action(_input.getChannel()), newValue);
30+ inputRecorder->setActionState(actionName, newValue);
3031 }
3132
3233 void ActionEndpoint::apply(const Pose& value, const Pointer& source) {
3334 _currentPose = value;
3435 InputRecorder* inputRecorder = InputRecorder::getInstance();
35− inputRecorder->setActionState(Action(_input.getChannel()), _currentPose);
36− if (inputRecorder->isPlayingback()) {
37− _currentPose = inputRecorder->getPoseState(Action(_input.getChannel()));
38− }
36+ auto userInputMapper = DependencyManager::get<UserInputMapper>();
37+ QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
38+ inputRecorder->setActionState(actionName, _currentPose);
3939
4040 if (!_currentPose.isValid()) {
4141 return;
4242 }
4343 if (_input != Input::INVALID_INPUT) {
44− auto userInputMapper = DependencyManager::get<UserInputMapper>();
4544 userInputMapper->setActionState(Action(_input.getChannel()), _currentPose);
4645 }
4746 }
libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h+11−0
@@ -12,6 +12,11 @@ · 11 unmodified lines
1212
1313 #include "../Endpoint.h"
1414
15+#include <DependencyManager.h>
16+
17+#include "../../InputRecorder.h"
18+#include "../../UserInputMapper.h"
19+
1520 namespace controller {
1621
1722 class StandardEndpoint : public VirtualEndpoint {
@@ -40,6 +45,12 @@ public: · 22 unmodified lines
4045
4146 virtual Pose pose() override {
4247 _read = true;
48+ InputRecorder* inputRecorder = InputRecorder::getInstance();
49+ if (inputRecorder->isPlayingback()) {
50+ auto userInputMapper = DependencyManager::get<UserInputMapper>();
51+ QString actionName = userInputMapper->getStandardPoseName(_input.getChannel());
52+ return inputRecorder->getPoseState(actionName);
53+ }
4354 return VirtualEndpoint::pose();
4455 }
4556
libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h+3−3
@@ -21,9 +21,9 @@ namespace controller { · 20 unmodified lines
2121 LowVelocityFilter(float rotationConstant, float translationConstant) :
2222 _translationConstant(translationConstant), _rotationConstant(rotationConstant) {}
2323
24− virtual float apply(float value) const override { return value; }
25− virtual Pose apply(Pose newPose) const;
26− virtual bool parseParameters(const QJsonValue& parameters) override;
24+ float apply(float value) const override { return value; }
25+ Pose apply(Pose newPose) const override;
26+ bool parseParameters(const QJsonValue& parameters) override;
2727
2828 private:
2929 float _translationConstant { 0.1f };
libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp+12−0
@@ -339,6 +339,18 @@ void OpenGLDisplayPlugin::deactivate() { · 338 unmodified lines
339339 Parent::deactivate();
340340 }
341341
342+bool OpenGLDisplayPlugin::startStandBySession() {
343+ if (!activateStandBySession()) {
344+ return false;
345+ }
346+ return Parent::startStandBySession();
347+}
348+
349+void OpenGLDisplayPlugin::endSession() {
350+ deactivateSession();
351+ Parent::endSession();
352+}
353+
342354 void OpenGLDisplayPlugin::customizeContext() {
343355 auto presentThread = DependencyManager::get<PresentThread>();
344356 Q_ASSERT(thread() == presentThread->thread());
libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h+6−0
@@ -42,6 +42,8 @@ public: · 41 unmodified lines
4242 // between the main thread and the presentation thread
4343 bool activate() override final;
4444 void deactivate() override final;
45+ bool startStandBySession() override final;
46+ void endSession() override final;
4547 bool eventFilter(QObject* receiver, QEvent* event) override;
4648 bool isDisplayVisible() const override { return true; }
4749
@@ -99,6 +101,10 @@ protected: · 51 unmodified lines
99101 // Returns true on successful activation
100102 virtual bool internalActivate() { return true; }
101103 virtual void internalDeactivate() {}
104+
105+ // Returns true on successful activation of standby session
106+ virtual bool activateStandBySession() { return true; }
107+ virtual void deactivateSession() {}
102108
103109 // Plugin specific functionality to send the composed scene to the output window or device
104110 virtual void internalPresent();
libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp+6−0
@@ -221,6 +221,12 @@ void HmdDisplayPlugin::internalPresent() { · 220 unmodified lines
221221 float shiftLeftBy = getLeftCenterPixel() - (sourceSize.x / 2);
222222 float newWidth = sourceSize.x - shiftLeftBy;
223223
224+ // Experimentally adjusted the region presented in preview to avoid seeing the masked pixels and recenter the center...
225+ static float SCALE_WIDTH = 0.9f;
226+ static float SCALE_OFFSET = 2.0f;
227+ newWidth *= SCALE_WIDTH;
228+ shiftLeftBy *= SCALE_OFFSET;
229+
224230 const unsigned int RATIO_Y = 9;
225231 const unsigned int RATIO_X = 16;
226232 glm::uvec2 originalClippedSize { newWidth, newWidth * RATIO_Y / RATIO_X };
libraries/entities-renderer/src/EntityTreeRenderer.cpp+1−221
@@ -23,7 +23,6 @@ · 22 unmodified lines
2323 #include <PerfStat.h>
2424 #include <SceneScriptingInterface.h>
2525 #include <ScriptEngine.h>
26−#include <procedural/ProceduralSkybox.h>
2726
2827 #include "EntityTreeRenderer.h"
2928
@@ -141,7 +140,6 @@ void EntityTreeRenderer::clear() { · 111 unmodified lines
141140
142141 // reset the zone to the default (while we load the next scene)
143142 _layeredZones.clear();
144− applyZoneAndHasSkybox(nullptr);
145143
146144 OctreeRenderer::clear();
147145 }
@@ -194,15 +192,7 @@ void EntityTreeRenderer::update() { · 46 unmodified lines
194192 tree->update();
195193
196194 // Handle enter/leave entity logic
197− bool updated = checkEnterLeaveEntities();
198−
199− // If we haven't already updated and previously attempted to load a texture,
200− // check if the texture loaded and apply it
201− if (!updated &&
202− ((_pendingAmbientTexture && (!_ambientTexture || _ambientTexture->isLoaded())) ||
203− (_pendingSkyboxTexture && (!_skyboxTexture || _skyboxTexture->isLoaded())))) {
204− applySkyboxAndHasAmbient();
205− }
195+ checkEnterLeaveEntities();
206196
207197 // Even if we're not moving the mouse, if we started clicking on an entity and we have
208198 // not yet released the hold then this is still considered a holdingClickOnEntity event
@@ -371,176 +361,6 @@ bool EntityTreeRenderer::applyLayeredZones() { · 162 unmodified lines
371361 return true;
372362 }
373363
374−
375−bool EntityTreeRenderer::applyZoneAndHasSkybox(const std::shared_ptr<ZoneEntityItem>& zone) {
376− auto textureCache = DependencyManager::get<TextureCache>();
377− auto scene = DependencyManager::get<SceneScriptingInterface>();
378− auto sceneStage = scene->getStage();
379− auto skyStage = scene->getSkyStage();
380− auto sceneKeyLight = sceneStage->getKeyLight();
381−
382− // If there is no zone, use the default background
383− if (!zone) {
384− _zoneUserData = QString();
385− skyStage->getSkybox()->clear();
386−
387− _pendingSkyboxTexture = false;
388− _skyboxTexture.clear();
389−
390− _pendingAmbientTexture = false;
391− _ambientTexture.clear();
392−
393− sceneKeyLight->resetAmbientSphere();
394− sceneKeyLight->setAmbientMap(nullptr);
395−
396− skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT);
397− return false;
398− }
399−
400− // Set the keylight
401− sceneKeyLight->setColor(ColorUtils::toVec3(zone->getKeyLightProperties().getColor()));
402− sceneKeyLight->setIntensity(zone->getKeyLightProperties().getIntensity());
403− sceneKeyLight->setAmbientIntensity(zone->getKeyLightProperties().getAmbientIntensity());
404− sceneKeyLight->setDirection(zone->getKeyLightProperties().getDirection());
405−
406− // Set the stage
407− bool isSunModelEnabled = zone->getStageProperties().getSunModelEnabled();
408− sceneStage->setSunModelEnable(isSunModelEnabled);
409− if (isSunModelEnabled) {
410− sceneStage->setLocation(zone->getStageProperties().getLongitude(),
411− zone->getStageProperties().getLatitude(),
412− zone->getStageProperties().getAltitude());
413−
414− auto sceneTime = sceneStage->getTime();
415− sceneTime->setHour(zone->getStageProperties().calculateHour());
416− sceneTime->setDay(zone->getStageProperties().calculateDay());
417− }
418−
419− // Set the ambient texture
420− _ambientTextureURL = zone->getKeyLightProperties().getAmbientURL();
421− if (_ambientTextureURL.isEmpty()) {
422− _pendingAmbientTexture = false;
423− _ambientTexture.clear();
424− } else {
425− _pendingAmbientTexture = true;
426− }
427−
428− // Set the skybox texture
429− return layerZoneAndHasSkybox(zone);
430−}
431−
432−bool EntityTreeRenderer::layerZoneAndHasSkybox(const std::shared_ptr<ZoneEntityItem>& zone) {
433− assert(zone);
434−
435− auto textureCache = DependencyManager::get<TextureCache>();
436− auto scene = DependencyManager::get<SceneScriptingInterface>();
437− auto skyStage = scene->getSkyStage();
438− auto skybox = skyStage->getSkybox();
439−
440− bool hasSkybox = false;
441−
442− switch (zone->getBackgroundMode()) {
443− case BACKGROUND_MODE_SKYBOX:
444− hasSkybox = true;
445−
446− skybox->setColor(zone->getSkyboxProperties().getColorVec3());
447−
448− if (_zoneUserData != zone->getUserData()) {
449− _zoneUserData = zone->getUserData();
450− std::dynamic_pointer_cast<ProceduralSkybox>(skybox)->parse(_zoneUserData);
451− }
452−
453− _skyboxTextureURL = zone->getSkyboxProperties().getURL();
454− if (_skyboxTextureURL.isEmpty()) {
455− _pendingSkyboxTexture = false;
456− _skyboxTexture.clear();
457− } else {
458− _pendingSkyboxTexture = true;
459− }
460−
461− applySkyboxAndHasAmbient();
462− skyStage->setBackgroundMode(model::SunSkyStage::SKY_BOX);
463−
464− break;
465−
466− case BACKGROUND_MODE_INHERIT:
467− default:
468− // Clear the skybox to release its textures
469− skybox->clear();
470− _zoneUserData = QString();
471−
472− _pendingSkyboxTexture = false;
473− _skyboxTexture.clear();
474−
475− // Let the application background through
476− if (applySkyboxAndHasAmbient()) {
477− skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT_TEXTURE);
478− } else {
479− skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT_AMBIENT_TEXTURE);
480− }
481−
482− break;
483− }
484−
485− return hasSkybox;
486−}
487−
488−bool EntityTreeRenderer::applySkyboxAndHasAmbient() {
489− auto textureCache = DependencyManager::get<TextureCache>();
490− auto scene = DependencyManager::get<SceneScriptingInterface>();
491− auto sceneStage = scene->getStage();
492− auto skyStage = scene->getSkyStage();
493− auto sceneKeyLight = sceneStage->getKeyLight();
494− auto skybox = skyStage->getSkybox();
495−
496− bool isAmbientSet = false;
497− if (_pendingAmbientTexture && !_ambientTexture) {
498− _ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::CUBE_TEXTURE);
499− }
500− if (_ambientTexture && _ambientTexture->isLoaded()) {
501− _pendingAmbientTexture = false;
502−
503− auto texture = _ambientTexture->getGPUTexture();
504− if (texture) {
505− isAmbientSet = true;
506− sceneKeyLight->setAmbientSphere(texture->getIrradiance());
507− sceneKeyLight->setAmbientMap(texture);
508− } else {
509− qCDebug(entitiesrenderer) << "Failed to load ambient texture:" << _ambientTexture->getURL();
510− }
511− }
512−
513− if (_pendingSkyboxTexture &&
514− (!_skyboxTexture || (_skyboxTexture->getURL() != _skyboxTextureURL))) {
515− _skyboxTexture = textureCache->getTexture(_skyboxTextureURL, image::TextureUsage::CUBE_TEXTURE);
516− }
517− if (_skyboxTexture && _skyboxTexture->isLoaded()) {
518− _pendingSkyboxTexture = false;
519−
520− auto texture = _skyboxTexture->getGPUTexture();
521− if (texture) {
522− skybox->setCubemap(texture);
523− if (!isAmbientSet) {
524− sceneKeyLight->setAmbientSphere(texture->getIrradiance());
525− sceneKeyLight->setAmbientMap(texture);
526− isAmbientSet = true;
527− }
528− } else {
529− qCDebug(entitiesrenderer) << "Failed to load skybox texture:" << _skyboxTexture->getURL();
530− skybox->setCubemap(nullptr);
531− }
532− } else {
533− skybox->setCubemap(nullptr);
534− }
535−
536− if (!isAmbientSet) {
537− sceneKeyLight->resetAmbientSphere();
538− sceneKeyLight->setAmbientMap(nullptr);
539− }
540−
541− return isAmbientSet;
542−}
543−
544364 const FBXGeometry* EntityTreeRenderer::getGeometryForEntity(EntityItemPointer entityItem) {
545365 const FBXGeometry* result = NULL;
546366
@@ -1186,8 +1006,6 @@ std::pair<EntityTreeRenderer::LayeredZones::iterator, bool> EntityTreeRenderer:: · 639 unmodified lines
11861006
11871007 void EntityTreeRenderer::LayeredZones::apply() {
11881008 assert(_entityTreeRenderer);
1189−
1190− applyPartial(begin());
11911009 }
11921010
11931011 void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zone) {
@@ -1202,12 +1020,6 @@ void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zo · 8 unmodified lines
12021020 } else {
12031021 LayeredZone zoneLayer(zone);
12041022
1205− // should we update? only if this zone is tighter than the current skybox zone
1206− bool shouldUpdate = false;
1207− if (_skyboxLayer == end() || zoneLayer <= *_skyboxLayer) {
1208− shouldUpdate = true;
1209− }
1210−
12111023 // find this zone's layer, if it exists
12121024 iterator layer = end();
12131025 auto it = _map.find(zoneLayer.id);
@@ -1227,41 +1039,9 @@ void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zo · 13 unmodified lines
12271039 std::tie(layer, std::ignore) = insert(zoneLayer);
12281040 _map.emplace(layer->id, layer);
12291041 }
1230−
1231− if (shouldUpdate) {
1232− applyPartial(layer);
1233− }
12341042 }
12351043 }
12361044
1237−void EntityTreeRenderer::LayeredZones::applyPartial(iterator layer) {
1238− bool hasSkybox = false;
1239− _skyboxLayer = end();
1240−
1241− if (layer == end()) {
1242− if (empty()) {
1243− _entityTreeRenderer->applyZoneAndHasSkybox(nullptr);
1244− return;
1245− } else { // a layer was removed - reapply from beginning
1246− layer = begin();
1247− }
1248− }
1249−
1250− if (layer == begin()) {
1251− hasSkybox = _entityTreeRenderer->applyZoneAndHasSkybox(layer->zone);
1252− } else {
1253− hasSkybox = _entityTreeRenderer->layerZoneAndHasSkybox(layer->zone);
1254− }
1255−
1256− if (layer != end()) {
1257− while (!hasSkybox && ++layer != end()) {
1258− hasSkybox = _entityTreeRenderer->layerZoneAndHasSkybox(layer->zone);
1259− }
1260− }
1261−
1262− _skyboxLayer = layer;
1263−}
1264−
12651045 bool EntityTreeRenderer::LayeredZones::contains(const LayeredZones& other) {
12661046 bool result = std::equal(other.begin(), other._skyboxLayer, begin());
12671047 if (result) {
libraries/entities-renderer/src/EntityTreeRenderer.h+0−3
@@ -147,9 +147,6 @@ private: · 146 unmodified lines
147147 void addEntityToScene(EntityItemPointer entity);
148148 bool findBestZoneAndMaybeContainingEntities(QVector<EntityItemID>* entitiesContainingAvatar = nullptr);
149149
150− bool applyZoneAndHasSkybox(const std::shared_ptr<ZoneEntityItem>& zone);
151− bool layerZoneAndHasSkybox(const std::shared_ptr<ZoneEntityItem>& zone);
152− bool applySkyboxAndHasAmbient();
153150 bool applyLayeredZones();
154151
155152 void checkAndCallPreload(const EntityItemID& entityID, bool reload = false, bool unloadFirst = false);
libraries/entities-renderer/src/RenderableModelEntityItem.cpp+1−1
@@ -605,7 +605,7 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori · 604 unmodified lines
605605
606606 QString extraInfo;
607607 return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance,
608− face, surfaceNormal, extraInfo, precisionPicking);
608+ face, surfaceNormal, extraInfo, precisionPicking, false);
609609 }
610610
611611 void RenderableModelEntityItem::getCollisionGeometryResource() {
libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp+3−0
@@ -13,6 +13,7 @@ · 12 unmodified lines
1313 #include <DependencyManager.h>
1414 #include <PerfStat.h>
1515 #include <GeometryCache.h>
16+#include <StencilMaskPass.h>
1617 #include <AbstractViewStateInterface.h>
1718 #include "EntitiesRendererLogging.h"
1819
@@ -292,6 +293,7 @@ void RenderableParticleEffectEntityItem::createPipelines() { · 273 unmodified lines
292293 state->setDepthTest(true, false, gpu::LESS_EQUAL);
293294 state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE,
294295 gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
296+ PrepareStencil::testMask(*state);
295297
296298 auto vertShader = gpu::Shader::createVertex(std::string(untextured_particle_vert));
297299 auto fragShader = gpu::Shader::createPixel(std::string(untextured_particle_frag));
@@ -305,6 +307,7 @@ void RenderableParticleEffectEntityItem::createPipelines() { · 7 unmodified lines
305307 state->setDepthTest(true, false, gpu::LESS_EQUAL);
306308 state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE,
307309 gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
310+ PrepareStencil::testMask(*state);
308311
309312 auto vertShader = gpu::Shader::createVertex(std::string(textured_particle_vert));
310313 auto fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag));
libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp+2−0
@@ -12,6 +12,7 @@ · 11 unmodified lines
1212 #include <glm/gtx/quaternion.hpp>
1313
1414 #include <GeometryCache.h>
15+#include <StencilMaskPass.h>
1516 #include <TextureCache.h>
1617 #include <PathUtils.h>
1718 #include <PerfStat.h>
@@ -69,6 +70,7 @@ void RenderablePolyLineEntityItem::createPipeline() { · 51 unmodified lines
6970
7071 gpu::StatePointer state = gpu::StatePointer(new gpu::State());
7172 state->setDepthTest(true, true, gpu::LESS_EQUAL);
73+ PrepareStencil::testMask(*state);
7274 state->setBlendFunction(true,
7375 gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
7476 gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp+5−0
@@ -46,6 +46,9 @@ · 45 unmodified lines
4646 #endif
4747
4848 #include "model/Geometry.h"
49+
50+#include "StencilMaskPass.h"
51+
4952 #include "EntityTreeRenderer.h"
5053 #include "polyvox_vert.h"
5154 #include "polyvox_frag.h"
@@ -743,6 +746,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { · 691 unmodified lines
743746 auto state = std::make_shared<gpu::State>();
744747 state->setCullMode(gpu::State::CULL_BACK);
745748 state->setDepthTest(true, true, gpu::LESS_EQUAL);
749+ PrepareStencil::testMaskDrawShape(*state);
746750
747751 _pipeline = gpu::Pipeline::create(program, state);
748752
@@ -750,6 +754,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { · 1 unmodified line
750754 wireframeState->setCullMode(gpu::State::CULL_BACK);
751755 wireframeState->setDepthTest(true, true, gpu::LESS_EQUAL);
752756 wireframeState->setFillMode(gpu::State::FILL_LINE);
757+ PrepareStencil::testMaskDrawShape(*wireframeState);
753758
754759 _wireframePipeline = gpu::Pipeline::create(program, wireframeState);
755760 }
libraries/entities-renderer/src/RenderableShapeEntityItem.cpp+2−0
@@ -13,6 +13,7 @@ · 12 unmodified lines
1313 #include <gpu/Batch.h>
1414
1515 #include <DependencyManager.h>
16+#include <StencilMaskPass.h>
1617 #include <GeometryCache.h>
1718 #include <PerfStat.h>
1819
@@ -93,6 +94,7 @@ void RenderableShapeEntityItem::render(RenderArgs* args) { · 74 unmodified lines
9394 _procedural->_fragmentSource = simple_frag;
9495 _procedural->_opaqueState->setCullMode(gpu::State::CULL_NONE);
9596 _procedural->_opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL);
97+ PrepareStencil::testMaskDrawShape(*_procedural->_opaqueState);
9698 _procedural->_opaqueState->setBlendFunction(false,
9799 gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
98100 gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
libraries/entities-renderer/src/RenderableWebEntityItem.cpp+4−4
@@ -127,7 +127,7 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer> · 126 unmodified lines
127127
128128 _webSurface->resume();
129129 _webSurface->getRootItem()->setProperty("url", _sourceUrl);
130− _webSurface->getRootContext()->setContextProperty("desktop", QVariant());
130+ _webSurface->getSurfaceContext()->setContextProperty("desktop", QVariant());
131131 // FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml.
132132
133133 // forward web events to EntityScriptingInterface
@@ -271,7 +271,7 @@ void RenderableWebEntityItem::loadSourceURL() { · 137 unmodified lines
271271 });
272272
273273 _webSurface->getRootItem()->setProperty("url", _sourceUrl);
274− _webSurface->getRootContext()->setContextProperty("desktop", QVariant());
274+ _webSurface->getSurfaceContext()->setContextProperty("desktop", QVariant());
275275
276276 } else {
277277 _contentType = qmlContent;
@@ -284,7 +284,7 @@ void RenderableWebEntityItem::loadSourceURL() { · 6 unmodified lines
284284 _webSurface->getRootItem(), _webSurface.data());
285285 }
286286 }
287− _webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
287+ _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
288288 }
289289
290290
@@ -420,7 +420,7 @@ void RenderableWebEntityItem::update(const quint64& now) { · 129 unmodified lines
420420
421421 if (_webSurface) {
422422 // update globalPosition
423− _webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
423+ _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
424424 }
425425
426426 auto interval = now - _lastRenderTime;
libraries/entities-renderer/src/RenderableZoneEntityItem.cpp+408−38
@@ -13,14 +13,90 @@ · 12 unmodified lines
1313
1414 #include <gpu/Batch.h>
1515
16+#include <model/Stage.h>
17+
1618 #include <AbstractViewStateInterface.h>
1719 #include <DependencyManager.h>
1820 #include <GeometryCache.h>
1921 #include <PerfStat.h>
22+#include <procedural/ProceduralSkybox.h>
2023
2124 #include "EntityTreeRenderer.h"
2225 #include "RenderableEntityItem.h"
2326
27+#include <LightPayload.h>
28+#include "DeferredLightingEffect.h"
29+
30+
31+class RenderableZoneEntityItemMeta {
32+public:
33+ RenderableZoneEntityItemMeta(EntityItemPointer entity);
34+ ~RenderableZoneEntityItemMeta();
35+
36+ typedef render::Payload<RenderableZoneEntityItemMeta> Payload;
37+ typedef Payload::DataPointer Pointer;
38+
39+ EntityItemPointer entity;
40+
41+ void render(RenderArgs* args);
42+
43+ void setVisible(bool visible) { _isVisible = visible; }
44+ bool isVisible() const { return _isVisible; }
45+
46+ render::Item::Bound& editBound() { _needUpdate = true; return _bound; }
47+
48+ model::LightPointer editSunLight() { _needSunUpdate = true; return _sunLight; }
49+ model::LightPointer editAmbientLight() { _needAmbientUpdate = true; return _ambientLight; }
50+ model::SunSkyStagePointer editBackground() { _needBackgroundUpdate = true; return _background; }
51+ model::SkyboxPointer editSkybox() { return editBackground()->getSkybox(); }
52+
53+ void setAmbientURL(const QString& ambientUrl);
54+
55+ void setSkyboxURL(const QString& skyboxUrl);
56+
57+ void setBackgroundMode(BackgroundMode mode);
58+ void setSkyboxColor(const glm::vec3& color);
59+ void setProceduralUserData(QString userData);
60+
61+protected:
62+ render::Item::Bound _bound;
63+
64+ model::LightPointer _sunLight;
65+ model::LightPointer _ambientLight;
66+ model::SunSkyStagePointer _background;
67+ BackgroundMode _backgroundMode { BACKGROUND_MODE_INHERIT };
68+
69+ LightStagePointer _stage;
70+ LightStage::Index _sunIndex { LightStage::INVALID_INDEX };
71+ LightStage::Index _ambientIndex { LightStage::INVALID_INDEX };
72+
73+ BackgroundStagePointer _backgroundStage;
74+ BackgroundStage::Index _backgroundIndex { BackgroundStage::INVALID_INDEX };
75+
76+ bool _needUpdate { true };
77+ bool _needSunUpdate { true };
78+ bool _needAmbientUpdate { true };
79+ bool _needBackgroundUpdate { true };
80+ bool _isVisible { true };
81+
82+
83+ void updateAmbientMap();
84+ void updateSkyboxMap();
85+
86+ // More attributes used for rendering:
87+ QString _ambientTextureURL;
88+ NetworkTexturePointer _ambientTexture;
89+ bool _pendingAmbientTexture { false };
90+ bool _validAmbientTexture { false };
91+
92+ QString _skyboxTextureURL;
93+ NetworkTexturePointer _skyboxTexture;
94+ bool _pendingSkyboxTexture { false };
95+ bool _validSkyboxTexture { false };
96+
97+ QString _proceduralUserData;
98+};
99+
24100 // Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1
25101 // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down.
26102 static const float SPHERE_ENTITY_SCALE = 0.5f;
@@ -67,8 +143,15 @@ bool RenderableZoneEntityItem::setProperties(const EntityItemProperties& propert · 40 unmodified lines
67143
68144 void RenderableZoneEntityItem::somethingChangedNotification() {
69145 DependencyManager::get<EntityTreeRenderer>()->updateZone(_id);
146+
147+ // If graphics elements are changed, we need to update the render items
148+ notifyChangedRenderItem();
149+
150+ // Poopagate back to parent
151+ ZoneEntityItem::somethingChangedNotification();
70152 }
71153
154+
72155 int RenderableZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
73156 ReadBitstreamToTreeParams& args,
74157 EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
@@ -174,18 +257,157 @@ bool RenderableZoneEntityItem::contains(const glm::vec3& point) const { · 99 unmodified lines
174257 return false;
175258 }
176259
177−class RenderableZoneEntityItemMeta {
178−public:
179− RenderableZoneEntityItemMeta(EntityItemPointer entity) : entity(entity){ }
180− typedef render::Payload<RenderableZoneEntityItemMeta> Payload;
181− typedef Payload::DataPointer Pointer;
260+bool RenderableZoneEntityItem::addToScene(EntityItemPointer self, const render::ScenePointer& scene,
261+ render::Transaction& transaction) {
262+ _myMetaItem = scene->allocateID();
263+
264+ auto renderData = std::make_shared<RenderableZoneEntityItemMeta>(self);
265+ auto renderPayload = std::make_shared<RenderableZoneEntityItemMeta::Payload>(renderData);
266+ updateKeyZoneItemFromEntity((*renderData));
267+ updateKeySunFromEntity((*renderData));
268+ updateKeyAmbientFromEntity((*renderData));
269+ updateKeyBackgroundFromEntity((*renderData));
270+
271+ render::Item::Status::Getters statusGetters;
272+ makeEntityItemStatusGetters(getThisPointer(), statusGetters);
273+ renderPayload->addStatusGetters(statusGetters);
274+
275+ transaction.resetItem(_myMetaItem, renderPayload);
276+
277+ return true;
278+}
279+
280+void RenderableZoneEntityItem::removeFromScene(EntityItemPointer self, const render::ScenePointer& scene,
281+ render::Transaction& transaction) {
282+ transaction.removeItem(_myMetaItem);
283+ render::Item::clearID(_myMetaItem);
284+ if (_model) {
285+ _model->removeFromScene(scene, transaction);
286+ }
287+}
288+
289+void RenderableZoneEntityItem::notifyBoundChanged() {
290+ notifyChangedRenderItem();
291+}
292+
293+void RenderableZoneEntityItem::updateKeySunFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) {
294+ auto sunLight = keyZonePayload.editSunLight();
295+ sunLight->setType(model::Light::SUN);
296+
297+ sunLight->setPosition(this->getPosition());
298+ sunLight->setOrientation(this->getRotation());
299+
300+ // Set the keylight
301+ sunLight->setColor(ColorUtils::toVec3(this->getKeyLightProperties().getColor()));
302+ sunLight->setIntensity(this->getKeyLightProperties().getIntensity());
303+ sunLight->setDirection(this->getKeyLightProperties().getDirection());
304+}
305+
306+void RenderableZoneEntityItem::updateKeyAmbientFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) {
307+ auto ambientLight = keyZonePayload.editAmbientLight();
308+ ambientLight->setType(model::Light::AMBIENT);
309+
310+ ambientLight->setPosition(this->getPosition());
311+ ambientLight->setOrientation(this->getRotation());
312+
313+
314+ // Set the keylight
315+ ambientLight->setAmbientIntensity(this->getKeyLightProperties().getAmbientIntensity());
316+
317+ if (this->getKeyLightProperties().getAmbientURL().isEmpty()) {
318+ keyZonePayload.setAmbientURL(this->getSkyboxProperties().getURL());
319+ } else {
320+ keyZonePayload.setAmbientURL(this->getKeyLightProperties().getAmbientURL());
321+ }
322+
323+}
324+
325+void RenderableZoneEntityItem::updateKeyBackgroundFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) {
326+ auto background = keyZonePayload.editBackground();
327+
328+ keyZonePayload.setBackgroundMode(this->getBackgroundMode());
329+ keyZonePayload.setSkyboxColor(this->getSkyboxProperties().getColorVec3());
330+ keyZonePayload.setProceduralUserData(this->getUserData());
331+ keyZonePayload.setSkyboxURL(this->getSkyboxProperties().getURL());
332+}
333+
334+
335+void RenderableZoneEntityItem::updateKeyZoneItemFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) {
336+
337+ keyZonePayload.setVisible(this->getVisible());
338+
339+ bool success;
340+ keyZonePayload.editBound() = this->getAABox(success);
341+ if (!success) {
342+ keyZonePayload.editBound() = render::Item::Bound();
343+ }
344+
345+ /* TODO: Implement the sun model behavior / Keep this code here for reference, this is how we
346+ {
347+ // Set the stage
348+ bool isSunModelEnabled = this->getStageProperties().getSunModelEnabled();
349+ sceneStage->setSunModelEnable(isSunModelEnabled);
350+ if (isSunModelEnabled) {
351+ sceneStage->setLocation(this->getStageProperties().getLongitude(),
352+ this->getStageProperties().getLatitude(),
353+ this->getStageProperties().getAltitude());
354+
355+ auto sceneTime = sceneStage->getTime();
356+ sceneTime->setHour(this->getStageProperties().calculateHour());
357+ sceneTime->setDay(this->getStageProperties().calculateDay());
358+ }
359+ }*/
360+}
361+
362+
363+void RenderableZoneEntityItem::sceneUpdateRenderItemFromEntity(render::Transaction& transaction) {
364+ if (!render::Item::isValidID(_myMetaItem)) {
365+ return;
366+ }
367+
368+ bool sunChanged = _keyLightPropertiesChanged;
369+ bool backgroundChanged = _backgroundPropertiesChanged;
370+ bool skyboxChanged = _skyboxPropertiesChanged;
371+
372+ transaction.updateItem<RenderableZoneEntityItemMeta>(_myMetaItem, [=](RenderableZoneEntityItemMeta& data) {
373+
374+ updateKeyZoneItemFromEntity(data);
375+
376+ if (sunChanged) {
377+ updateKeySunFromEntity(data);
378+ }
379+
380+ if (sunChanged || skyboxChanged) {
381+ updateKeyAmbientFromEntity(data);
382+ }
383+ if (backgroundChanged || skyboxChanged) {
384+ updateKeyBackgroundFromEntity(data);
385+ }
386+ });
387+}
388+
389+void RenderableZoneEntityItem::notifyChangedRenderItem() {
390+ if (!render::Item::isValidID(_myMetaItem)) {
391+ return;
392+ }
393+
394+ render::Transaction transaction;
395+ render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
396+ sceneUpdateRenderItemFromEntity(transaction);
397+ scene->enqueueTransaction(transaction);
398+}
399+
400+
401+
402+
403+
404+
405+
182406
183− EntityItemPointer entity;
184−};
185407
186408 namespace render {
187409 template <> const ItemKey payloadGetKey(const RenderableZoneEntityItemMeta::Pointer& payload) {
188− return ItemKey::Builder::opaqueShape();
410+ return ItemKey::Builder().withTypeMeta().build();
189411 }
190412
191413 template <> const Item::Bound payloadGetBound(const RenderableZoneEntityItemMeta::Pointer& payload) {
@@ -200,51 +422,199 @@ namespace render { · 8 unmodified lines
200422 return render::Item::Bound();
201423 }
202424 template <> void payloadRender(const RenderableZoneEntityItemMeta::Pointer& payload, RenderArgs* args) {
203− if (args) {
204− if (payload && payload->entity) {
205− payload->entity->render(args);
206− }
207− }
425+ payload->render(args);
208426 }
209427 }
210428
211−bool RenderableZoneEntityItem::addToScene(EntityItemPointer self, const render::ScenePointer& scene,
212− render::Transaction& transaction) {
213− _myMetaItem = scene->allocateID();
429+RenderableZoneEntityItemMeta::RenderableZoneEntityItemMeta(EntityItemPointer entity) :
430+ entity(entity),
431+ _sunLight(std::make_shared<model::Light>()),
432+ _ambientLight(std::make_shared<model::Light>()),
433+ _background(std::make_shared<model::SunSkyStage>())
434+{
435+ _background->setSkybox(std::make_shared<ProceduralSkybox>());
436+}
214437
215− auto renderData = std::make_shared<RenderableZoneEntityItemMeta>(self);
216− auto renderPayload = std::make_shared<RenderableZoneEntityItemMeta::Payload>(renderData);
217438
218− render::Item::Status::Getters statusGetters;
219− makeEntityItemStatusGetters(getThisPointer(), statusGetters);
220− renderPayload->addStatusGetters(statusGetters);
439+RenderableZoneEntityItemMeta::~RenderableZoneEntityItemMeta() {
440+ if (_stage) {
441+ if (!LightStage::isIndexInvalid(_sunIndex)) {
442+ _stage->removeLight(_sunIndex);
443+ }
444+ if (!LightStage::isIndexInvalid(_ambientIndex)) {
445+ _stage->removeLight(_ambientIndex);
446+
447+ }
448+ }
221449
222− transaction.resetItem(_myMetaItem, renderPayload);
223− return true;
450+ if (_backgroundStage) {
451+ if (!BackgroundStage::isIndexInvalid(_backgroundIndex)) {
452+ _backgroundStage->removeBackground(_backgroundIndex);
453+ }
454+ }
224455 }
225456
226−void RenderableZoneEntityItem::removeFromScene(EntityItemPointer self, const render::ScenePointer& scene,
227− render::Transaction& transaction) {
228− transaction.removeItem(_myMetaItem);
229− render::Item::clearID(_myMetaItem);
230− if (_model) {
231− _model->removeFromScene(scene, transaction);
457+void RenderableZoneEntityItemMeta::setAmbientURL(const QString& ambientUrl) {
458+ // nothing change if nothing change
459+ if (_ambientTextureURL == ambientUrl) {
460+ return;
461+ }
462+ _ambientTextureURL = ambientUrl;
463+
464+ if (_ambientTextureURL.isEmpty()) {
465+ _validAmbientTexture = false;
466+ _pendingAmbientTexture = false;
467+ _ambientTexture.clear();
468+
469+ _ambientLight->setAmbientMap(nullptr);
470+ _ambientLight->setAmbientSpherePreset(gpu::SphericalHarmonics::BREEZEWAY);
471+ } else {
472+ _pendingAmbientTexture = true;
473+ auto textureCache = DependencyManager::get<TextureCache>();
474+ _ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::CUBE_TEXTURE);
475+
476+ // keep whatever is assigned on the ambient map/sphere until texture is loaded
232477 }
233478 }
234479
480+void RenderableZoneEntityItemMeta::updateAmbientMap() {
481+ if (_pendingAmbientTexture) {
482+ if (_ambientTexture && _ambientTexture->isLoaded()) {
483+ _pendingAmbientTexture = false;
235484
236−void RenderableZoneEntityItem::notifyBoundChanged() {
237− if (!render::Item::isValidID(_myMetaItem)) {
485+ auto texture = _ambientTexture->getGPUTexture();
486+ if (texture) {
487+ if (texture->getIrradiance()) {
488+ _ambientLight->setAmbientSphere(*texture->getIrradiance());
489+ } else {
490+ _ambientLight->setAmbientSpherePreset(gpu::SphericalHarmonics::BREEZEWAY);
491+ }
492+ editAmbientLight()->setAmbientMap(texture);
493+ _validAmbientTexture = true;
494+ } else {
495+ qCDebug(entitiesrenderer) << "Failed to load ambient texture:" << _ambientTexture->getURL();
496+ }
497+ }
498+ }
499+}
500+
501+
502+void RenderableZoneEntityItemMeta::setSkyboxURL(const QString& skyboxUrl) {
503+ // nothing change if nothing change
504+ if (_skyboxTextureURL == skyboxUrl) {
238505 return;
239506 }
240− render::Transaction transaction;
241− render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
242− if (scene) {
243− transaction.updateItem<RenderableZoneEntityItemMeta>(_myMetaItem, [](RenderableZoneEntityItemMeta& data) {
244− });
507+ _skyboxTextureURL = skyboxUrl;
245508
246− scene->enqueueTransaction(transaction);
509+ if (_skyboxTextureURL.isEmpty()) {
510+ _validSkyboxTexture = false;
511+ _pendingSkyboxTexture = false;
512+ _skyboxTexture.clear();
513+
514+ editSkybox()->setCubemap(nullptr);
247515 } else {
248− qCWarning(entitiesrenderer) << "RenderableZoneEntityItem::notifyBoundChanged(), Unexpected null scene, possibly during application shutdown";
516+ _pendingSkyboxTexture = true;
517+ auto textureCache = DependencyManager::get<TextureCache>();
518+ _skyboxTexture = textureCache->getTexture(_skyboxTextureURL, image::TextureUsage::CUBE_TEXTURE);
519+ }
520+}
521+
522+void RenderableZoneEntityItemMeta::updateSkyboxMap() {
523+ if (_pendingSkyboxTexture) {
524+ if (_skyboxTexture && _skyboxTexture->isLoaded()) {
525+ _pendingSkyboxTexture = false;
526+
527+ auto texture = _skyboxTexture->getGPUTexture();
528+ if (texture) {
529+ editSkybox()->setCubemap(texture);
530+ _validSkyboxTexture = true;
531+ } else {
532+ qCDebug(entitiesrenderer) << "Failed to load Skybox texture:" << _skyboxTexture->getURL();
533+ }
534+ }
535+ }
536+}
537+
538+void RenderableZoneEntityItemMeta::setBackgroundMode(BackgroundMode mode) {
539+ _backgroundMode = mode;
540+}
541+
542+void RenderableZoneEntityItemMeta::setSkyboxColor(const glm::vec3& color) {
543+ editSkybox()->setColor(color);
544+}
545+
546+void RenderableZoneEntityItemMeta::setProceduralUserData(QString userData) {
547+ if (_proceduralUserData != userData) {
548+ _proceduralUserData = userData;
549+ std::dynamic_pointer_cast<ProceduralSkybox>(editSkybox())->parse(_proceduralUserData);
550+ }
551+}
552+
553+
554+
555+void RenderableZoneEntityItemMeta::render(RenderArgs* args) {
556+ if (!_stage) {
557+ _stage = DependencyManager::get<DeferredLightingEffect>()->getLightStage();
558+ }
559+
560+ if (!_backgroundStage) {
561+ _backgroundStage = DependencyManager::get<DeferredLightingEffect>()->getBackgroundStage();
562+ }
563+
564+ { // Sun
565+ // Need an update ?
566+ if (_needSunUpdate) {
567+ // Do we need to allocate the light in the stage ?
568+ if (LightStage::isIndexInvalid(_sunIndex)) {
569+ _sunIndex = _stage->addLight(_sunLight);
570+ } else {
571+ _stage->updateLightArrayBuffer(_sunIndex);
572+ }
573+ _needSunUpdate = false;
574+ }
575+ }
576+
577+ { // Ambient
578+ updateAmbientMap();
579+
580+ // Need an update ?
581+ if (_needAmbientUpdate) {
582+ // Do we need to allocate the light in the stage ?
583+ if (LightStage::isIndexInvalid(_ambientIndex)) {
584+ _ambientIndex = _stage->addLight(_ambientLight);
585+ } else {
586+ _stage->updateLightArrayBuffer(_ambientIndex);
587+ }
588+ _needAmbientUpdate = false;
589+ }
590+ }
591+
592+ { // Skybox
593+ updateSkyboxMap();
594+
595+ if (_needBackgroundUpdate) {
596+ if (BackgroundStage::isIndexInvalid(_backgroundIndex)) {
597+ _backgroundIndex = _backgroundStage->addBackground(_background);
598+ } else {
599+
600+ }
601+ _needBackgroundUpdate = false;
602+ }
603+ }
604+
605+ if (isVisible()) {
606+ // FInally, push the light visible in the frame
607+ // THe directional key light for sure
608+ _stage->_currentFrame.pushSunLight(_sunIndex);
609+
610+ // The ambient light only if it has a valid texture to render with
611+ if (_validAmbientTexture || _validSkyboxTexture) {
612+ _stage->_currentFrame.pushAmbientLight(_ambientIndex);
613+ }
614+
615+ // The background only if the mode is not inherit
616+ if (_backgroundMode != BACKGROUND_MODE_INHERIT) {
617+ _backgroundStage->_currentFrame.pushBackground(_backgroundIndex);
618+ }
249619 }
250620 }
libraries/entities-renderer/src/RenderableZoneEntityItem.h+14−3
@@ -16,6 +16,9 @@ · 15 unmodified lines
1616 #include <ZoneEntityItem.h>
1717
1818 class NetworkGeometry;
19+class KeyLightPayload;
20+
21+class RenderableZoneEntityItemMeta;
1922
2023 class RenderableZoneEntityItem : public ZoneEntityItem {
2124 public:
@@ -42,7 +45,7 @@ public: · 20 unmodified lines
4245 virtual void removeFromScene(EntityItemPointer self, const render::ScenePointer& scene, render::Transaction& transaction) override;
4346
4447 render::ItemID getRenderItemID() const { return _myMetaItem; }
45−
48+
4649 private:
4750 virtual void locationChanged(bool tellPhysics = true) override { EntityItem::locationChanged(tellPhysics); notifyBoundChanged(); }
4851 virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); }
@@ -52,10 +55,18 @@ private: · 3 unmodified lines
5255
5356 template<typename Lambda>
5457 void changeProperties(Lambda functor);
55−
58+
59+ void notifyChangedRenderItem();
60+ void sceneUpdateRenderItemFromEntity(render::Transaction& transaction);
61+ void updateKeyZoneItemFromEntity(RenderableZoneEntityItemMeta& keyZonePayload);
62+
63+ void updateKeySunFromEntity(RenderableZoneEntityItemMeta& keyZonePayload);
64+ void updateKeyAmbientFromEntity(RenderableZoneEntityItemMeta& keyZonePayload);
65+ void updateKeyBackgroundFromEntity(RenderableZoneEntityItemMeta& keyZonePayload);
66+
5667 ModelPointer _model;
5768 bool _needsInitialSimulation;
58−
69+
5970 render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID };
6071 };
6172
libraries/entities/src/EntityItem.cpp+7−17
@@ -681,7 +681,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef · 680 unmodified lines
681681 // and pretend that we own it (we assume we'll recover it soon)
682682
683683 // However, for now, when the server uses a newer time than what we sent, listen to what we're told.
684− if (overwriteLocalData) weOwnSimulation = false;
684+ if (overwriteLocalData) {
685+ weOwnSimulation = false;
686+ }
685687 } else if (_simulationOwner.set(newSimOwner)) {
686688 markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID);
687689 somethingChanged = true;
@@ -1293,27 +1295,15 @@ void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) c · 605 unmodified lines
12931295 properties._accelerationChanged = true;
12941296 }
12951297
1296−void EntityItem::pokeSimulationOwnership() {
1297− markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_POKE);
1298− auto nodeList = DependencyManager::get<NodeList>();
1299− if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) {
1300− // we already own it
1301− _simulationOwner.promotePriority(SCRIPT_POKE_SIMULATION_PRIORITY);
1302− } else {
1303− // we don't own it yet
1304− _simulationOwner.setPendingPriority(SCRIPT_POKE_SIMULATION_PRIORITY, usecTimestampNow());
1305− }
1306−}
1307−
1308−void EntityItem::grabSimulationOwnership() {
1309− markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB);
1298+void EntityItem::flagForOwnershipBid(uint8_t priority) {
1299+ markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY);
13101300 auto nodeList = DependencyManager::get<NodeList>();
13111301 if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) {
13121302 // we already own it
1313− _simulationOwner.promotePriority(SCRIPT_GRAB_SIMULATION_PRIORITY);
1303+ _simulationOwner.promotePriority(priority);
13141304 } else {
13151305 // we don't own it yet
1316− _simulationOwner.setPendingPriority(SCRIPT_GRAB_SIMULATION_PRIORITY, usecTimestampNow());
1306+ _simulationOwner.setPendingPriority(priority, usecTimestampNow());
13171307 }
13181308 }
13191309
libraries/entities/src/EntityItem.h+2−2
@@ -321,6 +321,7 @@ public: · 320 unmodified lines
321321 void updateSimulationOwner(const SimulationOwner& owner);
322322 void clearSimulationOwnership();
323323 void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp);
324+ uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); }
324325 void rememberHasSimulationOwnershipBid() const;
325326
326327 QString getMarketplaceID() const;
@@ -394,8 +395,7 @@ public: · 67 unmodified lines
394395
395396 void getAllTerseUpdateProperties(EntityItemProperties& properties) const;
396397
397− void pokeSimulationOwnership();
398− void grabSimulationOwnership();
398+ void flagForOwnershipBid(uint8_t priority);
399399 void flagForMotionStateChange() { _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; }
400400
401401 QString actionsToDebugString();
libraries/entities/src/EntityScriptingInterface.cpp+6−4
@@ -230,6 +230,8 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties · 229 unmodified lines
230230 }
231231
232232 entity->setLastBroadcast(usecTimestampNow());
233+ // since we're creating this object we will immediately volunteer to own its simulation
234+ entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
233235 propertiesWithSimID.setLastEdited(entity->getLastEdited());
234236 } else {
235237 qCDebug(entities) << "script failed to add new Entity to local Octree";
@@ -440,7 +442,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& · 204 unmodified lines
440442 } else {
441443 // we make a bid for simulation ownership
442444 properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);
443− entity->pokeSimulationOwnership();
445+ entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY);
444446 entity->rememberHasSimulationOwnershipBid();
445447 }
446448 }
@@ -1194,7 +1196,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, · 747 unmodified lines
11941196 }
11951197 action->setIsMine(true);
11961198 success = entity->addAction(simulation, action);
1197− entity->grabSimulationOwnership();
1199+ entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY);
11981200 return false; // Physics will cause a packet to be sent, so don't send from here.
11991201 });
12001202 if (success) {
@@ -1210,7 +1212,7 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& · 9 unmodified lines
12101212 return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
12111213 bool success = entity->updateAction(simulation, actionID, arguments);
12121214 if (success) {
1213− entity->grabSimulationOwnership();
1215+ entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY);
12141216 }
12151217 return success;
12161218 });
@@ -1224,7 +1226,7 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& · 7 unmodified lines
12241226 success = entity->removeAction(simulation, actionID);
12251227 if (success) {
12261228 // reduce from grab to poke
1227− entity->pokeSimulationOwnership();
1229+ entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY);
12281230 }
12291231 return false; // Physics will cause a packet to be sent, so don't send from here.
12301232 });
libraries/entities/src/SimulationFlags.h+1−3
@@ -26,12 +26,10 @@ namespace Simulation { · 25 unmodified lines
2626 const uint32_t DIRTY_MATERIAL = 0x00400;
2727 const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine
2828 const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed
29− const uint32_t DIRTY_SIMULATION_OWNERSHIP_FOR_POKE = 0x2000; // bid for simulation ownership at "poke"
30− const uint32_t DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB = 0x4000; // bid for simulation ownership at "grab"
29+ const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed
3130
3231 const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION;
3332 const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY;
34− const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = DIRTY_SIMULATION_OWNERSHIP_FOR_POKE | DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB;
3533 };
3634
3735 #endif // hifi_SimulationFlags_h
libraries/entities/src/SimulationOwner.cpp+11−11
@@ -26,9 +26,9 @@ const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1; · 25 unmodified lines
2626 SimulationOwner::SimulationOwner() :
2727 _id(),
2828 _expiry(0),
29− _pendingTimestamp(0),
29+ _pendingBidTimestamp(0),
3030 _priority(0),
31− _pendingPriority(0),
31+ _pendingBidPriority(0),
3232 _pendingState(PENDING_STATE_NOTHING)
3333 {
3434 }
@@ -36,9 +36,9 @@ SimulationOwner::SimulationOwner() : · 1 unmodified line
3636 SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) :
3737 _id(id),
3838 _expiry(0),
39− _pendingTimestamp(0),
39+ _pendingBidTimestamp(0),
4040 _priority(priority),
41− _pendingPriority(0)
41+ _pendingBidPriority(0)
4242 {
4343 }
4444
@@ -61,9 +61,9 @@ bool SimulationOwner::fromByteArray(const QByteArray& data) { · 16 unmodified lines
6161 void SimulationOwner::clear() {
6262 _id = QUuid();
6363 _expiry = 0;
64− _pendingTimestamp = 0;
64+ _pendingBidTimestamp = 0;
6565 _priority = 0;
66− _pendingPriority = 0;
66+ _pendingBidPriority = 0;
6767 _pendingState = PENDING_STATE_NOTHING;
6868 }
6969
@@ -102,9 +102,9 @@ bool SimulationOwner::set(const SimulationOwner& owner) { · 32 unmodified lines
102102 }
103103
104104 void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) {
105− _pendingPriority = priority;
106− _pendingTimestamp = timestamp;
107− _pendingState = (_pendingPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE;
105+ _pendingBidPriority = priority;
106+ _pendingBidTimestamp = timestamp;
107+ _pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE;
108108 }
109109
110110 void SimulationOwner::updateExpiry() {
@@ -113,11 +113,11 @@ void SimulationOwner::updateExpiry() { · 2 unmodified lines
113113 }
114114
115115 bool SimulationOwner::pendingRelease(const quint64& timestamp) {
116− return _pendingPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingTimestamp >= timestamp;
116+ return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp;
117117 }
118118
119119 bool SimulationOwner::pendingTake(const quint64& timestamp) {
120− return _pendingPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingTimestamp >= timestamp;
120+ return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp;
121121 }
122122
123123 void SimulationOwner::clearCurrentOwner() {
libraries/entities/src/SimulationOwner.h+3−2
@@ -66,6 +66,7 @@ public: · 65 unmodified lines
6666
6767 bool hasExpired() const { return usecTimestampNow() > _expiry; }
6868
69+ uint8_t getPendingPriority() const { return _pendingBidPriority; }
6970 bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE
7071 bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE
7172 void clearCurrentOwner();
@@ -84,9 +85,9 @@ public: · 12 unmodified lines
8485 private:
8586 QUuid _id; // owner
8687 quint64 _expiry; // time when ownership can transition at equal priority
87− quint64 _pendingTimestamp; // time when pending update was set
88+ quint64 _pendingBidTimestamp; // time when pending bid was set
8889 quint8 _priority; // priority of current owner
89− quint8 _pendingPriority; // priority of pendingTake
90+ quint8 _pendingBidPriority; // priority at which we'd like to own it
9091 quint8 _pendingState; // NOTHING, TAKE, or RELEASE
9192 };
9293
libraries/entities/src/ZoneEntityItem.cpp+39−20
@@ -71,9 +71,27 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { · 70 unmodified lines
7171 bool somethingChanged = false;
7272 somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
7373
74− bool somethingChangedInKeyLight = _keyLightProperties.setProperties(properties);
75−
76− bool somethingChangedInStage = _stageProperties.setProperties(properties);
74+ if (somethingChanged) {
75+ bool wantDebug = false;
76+ if (wantDebug) {
77+ uint64_t now = usecTimestampNow();
78+ int elapsed = now - getLastEdited();
79+ qCDebug(entities) << "ZoneEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
80+ "now=" << now << " getLastEdited()=" << getLastEdited();
81+ }
82+ setLastEdited(properties._lastEdited);
83+ }
84+
85+ return somethingChanged;
86+}
87+
88+bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& properties) {
89+ bool somethingChanged = EntityItem::setSubClassProperties(properties); // set the properties in our base class
90+
91+
92+ _keyLightPropertiesChanged = _keyLightProperties.setProperties(properties);
93+
94+ _stagePropertiesChanged = _stageProperties.setProperties(properties);
7795
7896 SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
7997 SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
@@ -83,20 +101,10 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { · 3 unmodified lines
83101 SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed);
84102 SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL);
85103
86− bool somethingChangedInSkybox = _skyboxProperties.setProperties(properties);
104+ _skyboxPropertiesChanged = _skyboxProperties.setProperties(properties);
87105
88− somethingChanged = somethingChanged || somethingChangedInKeyLight || somethingChangedInStage || somethingChangedInSkybox;
106+ somethingChanged = somethingChanged || _keyLightPropertiesChanged || _stagePropertiesChanged || _skyboxPropertiesChanged;
89107
90− if (somethingChanged) {
91− bool wantDebug = false;
92− if (wantDebug) {
93− uint64_t now = usecTimestampNow();
94− int elapsed = now - getLastEdited();
95− qCDebug(entities) << "ZoneEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
96− "now=" << now << " getLastEdited()=" << getLastEdited();
97− }
98− setLastEdited(properties._lastEdited);
99− }
100108
101109 return somethingChanged;
102110 }
@@ -109,14 +117,14 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, · 6 unmodified lines
109117 const unsigned char* dataAt = data;
110118
111119 int bytesFromKeylight = _keyLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
112− propertyFlags, overwriteLocalData, somethingChanged);
113−
120+ propertyFlags, overwriteLocalData, _keyLightPropertiesChanged);
121+ somethingChanged = somethingChanged || _keyLightPropertiesChanged;
114122 bytesRead += bytesFromKeylight;
115123 dataAt += bytesFromKeylight;
116124
117125 int bytesFromStage = _stageProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
118− propertyFlags, overwriteLocalData, somethingChanged);
119−
126+ propertyFlags, overwriteLocalData, _stagePropertiesChanged);
127+ somethingChanged = somethingChanged || _stagePropertiesChanged;
120128 bytesRead += bytesFromStage;
121129 dataAt += bytesFromStage;
122130
@@ -125,7 +133,8 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, · 2 unmodified lines
125133 READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode);
126134
127135 int bytesFromSkybox = _skyboxProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
128− propertyFlags, overwriteLocalData, somethingChanged);
136+ propertyFlags, overwriteLocalData, _skyboxPropertiesChanged);
137+ somethingChanged = somethingChanged || _skyboxPropertiesChanged;
129138 bytesRead += bytesFromSkybox;
130139 dataAt += bytesFromSkybox;
131140
@@ -185,6 +194,16 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits · 53 unmodified lines
185194 APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, getFilterURL());
186195 }
187196
197+void ZoneEntityItem::somethingChangedNotification() {
198+ EntityItem::somethingChangedNotification();
199+ withWriteLock([&] {
200+ _keyLightPropertiesChanged = false;
201+ _backgroundPropertiesChanged = false;
202+ _stagePropertiesChanged = false;
203+ _skyboxPropertiesChanged = false;
204+ });
205+}
206+
188207 void ZoneEntityItem::debugDump() const {
189208 quint64 now = usecTimestampNow();
190209 qCDebug(entities) << " ZoneEntityItem id:" << getEntityItemID() << "---------------------------------------------";
libraries/entities/src/ZoneEntityItem.h+15−1
@@ -29,10 +29,16 @@ public: · 28 unmodified lines
2929 // methods for getting/setting all properties of an entity
3030 virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
3131 virtual bool setProperties(const EntityItemProperties& properties) override;
32+ virtual bool setSubClassProperties(const EntityItemProperties& properties) override;
3233
3334 // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
3435 virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
3536
37+ /// Override this in your derived class if you'd like to be informed when something about the state of the entity
38+ /// has changed. This will be called with properties change or when new data is loaded from a stream
39+ /// Overriding this function to capture the information that a keylight / Ambient / skybox properties has changed
40+ virtual void somethingChangedNotification() override;
41+
3642 virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
3743 EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
3844 EntityPropertyFlags& requestedProperties,
@@ -64,7 +70,7 @@ public: · 25 unmodified lines
6470
6571 const KeyLightPropertyGroup& getKeyLightProperties() const { return _keyLightProperties; }
6672
67− void setBackgroundMode(BackgroundMode value) { _backgroundMode = value; }
73+ void setBackgroundMode(BackgroundMode value) { _backgroundMode = value; _backgroundPropertiesChanged = true; }
6874 BackgroundMode getBackgroundMode() const { return _backgroundMode; }
6975
7076 const SkyboxPropertyGroup& getSkyboxProperties() const { return _skyboxProperties; }
@@ -106,6 +112,14 @@ protected: · 35 unmodified lines
106112 bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED };
107113 QString _filterURL { DEFAULT_FILTER_URL };
108114
115+ // Dirty flags turn true when either keylight properties is changing values.
116+ // This gets back to false in the somethingChangedNotification() call
117+ // Which is called after a setProperties() or a readEntitySubClassFromBUfferCall on the entity.
118+ bool _keyLightPropertiesChanged { false };
119+ bool _backgroundPropertiesChanged { false };
120+ bool _skyboxPropertiesChanged { false };
121+ bool _stagePropertiesChanged { false };
122+
109123 static bool _drawZoneBoundaries;
110124 static bool _zonesArePickable;
111125 };
libraries/fbx/src/FBXReader.cpp+4−10
@@ -460,17 +460,11 @@ FBXLight extractLight(const FBXNode& object) { · 459 unmodified lines
460460 }
461461
462462 QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
463− QString path = QFileInfo(url).path();
464− QByteArray filename = filepath;
465− QFileInfo checkFile(path + "/" + filepath);
466−
467− // check if the file exists at the RelativeFilename
468− if (!(checkFile.exists() && checkFile.isFile())) {
469− // if not, assume it is in the fbx directory
470− filename = filename.mid(filename.lastIndexOf('/') + 1);
471− }
463+ // in order to match the behaviour when loading models from remote URLs
464+ // we assume that all external textures are right beside the loaded model
465+ // ignoring any relative paths or absolute paths inside of models
472466
473− return filename;
467+ return filepath.mid(filepath.lastIndexOf('/') + 1);
474468 }
475469
476470 FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) {
libraries/gl/CMakeLists.txt+1−1
@@ -1,6 +1,6 @@
11 set(TARGET_NAME gl)
22 setup_hifi_library(OpenGL Qml Quick)
3−link_hifi_libraries(shared)
3+link_hifi_libraries(shared networking)
44
55 target_opengl()
66
libraries/gl/src/gl/OffscreenQmlSurface.cpp+152−75
@@ -33,6 +33,9 @@ · 32 unmodified lines
3333 #include <NetworkAccessManager.h>
3434 #include <GLMHelpers.h>
3535 #include <shared/GlobalAppProperties.h>
36+#include <FileTypeProfile.h>
37+#include <HFWebEngineProfile.h>
38+#include <HFTabletWebEngineProfile.h>
3639
3740 #include "OffscreenGLCanvas.h"
3841 #include "GLHelpers.h"
@@ -41,6 +44,7 @@ · 2 unmodified lines
4144
4245 Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml")
4346 Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl")
47+Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
4448
4549 struct TextureSet {
4650 // The number of surfaces with this size
@@ -254,8 +258,89 @@ QNetworkAccessManager* QmlNetworkAccessManagerFactory::create(QObject* parent) { · 207 unmodified lines
254258 return new QmlNetworkAccessManager(parent);
255259 }
256260
257−Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
258−Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
261+static QQmlEngine* globalEngine { nullptr };
262+static size_t globalEngineRefCount { 0 };
263+
264+QString getEventBridgeJavascript() {
265+ // FIXME: Refactor with similar code in RenderableWebEntityItem
266+ QString javaScriptToInject;
267+ QFile webChannelFile(":qtwebchannel/qwebchannel.js");
268+ QFile createGlobalEventBridgeFile(PathUtils::resourcesPath() + "/html/createGlobalEventBridge.js");
269+ if (webChannelFile.open(QFile::ReadOnly | QFile::Text) &&
270+ createGlobalEventBridgeFile.open(QFile::ReadOnly | QFile::Text)) {
271+ QString webChannelStr = QTextStream(&webChannelFile).readAll();
272+ QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll();
273+ javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
274+ } else {
275+ qCWarning(glLogging) << "Unable to find qwebchannel.js or createGlobalEventBridge.js";
276+ }
277+ return javaScriptToInject;
278+}
279+
280+class EventBridgeWrapper : public QObject {
281+ Q_OBJECT
282+ Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT);
283+
284+public:
285+ EventBridgeWrapper(QObject* eventBridge, QObject* parent = nullptr) : QObject(parent), _eventBridge(eventBridge) {
286+ }
287+
288+ QObject* getEventBridge() {
289+ return _eventBridge;
290+ }
291+
292+private:
293+ QObject* _eventBridge;
294+};
295+
296+
297+
298+QQmlEngine* acquireEngine(QQuickWindow* window) {
299+ Q_ASSERT(QThread::currentThread() == qApp->thread());
300+ if (!globalEngine) {
301+ Q_ASSERT(0 == globalEngineRefCount);
302+ globalEngine = new QQmlEngine();
303+ globalEngine->setNetworkAccessManagerFactory(new QmlNetworkAccessManagerFactory);
304+
305+ auto importList = globalEngine->importPathList();
306+ importList.insert(importList.begin(), PathUtils::resourcesPath());
307+ globalEngine->setImportPathList(importList);
308+ for (const auto& path : importList) {
309+ qDebug() << path;
310+ }
311+
312+ if (!globalEngine->incubationController()) {
313+ globalEngine->setIncubationController(window->incubationController());
314+ }
315+ auto rootContext = globalEngine->rootContext();
316+ rootContext->setContextProperty("GL", ::getGLContextData());
317+ rootContext->setContextProperty("urlHandler", new UrlHandler());
318+ rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath()));
319+ rootContext->setContextProperty("pathToFonts", "../../");
320+ rootContext->setContextProperty("ApplicationInterface", qApp);
321+ auto javaScriptToInject = getEventBridgeJavascript();
322+ if (!javaScriptToInject.isEmpty()) {
323+ rootContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject));
324+ }
325+ rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext));
326+ rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext));
327+ rootContext->setContextProperty("HFTabletWebEngineProfile", new HFTabletWebEngineProfile(rootContext));
328+
329+
330+ }
331+
332+ ++globalEngineRefCount;
333+ return globalEngine;
334+}
335+
336+void releaseEngine() {
337+ Q_ASSERT(QThread::currentThread() == qApp->thread());
338+ Q_ASSERT(0 != globalEngineRefCount);
339+ if (0 == --globalEngineRefCount) {
340+ globalEngine->deleteLater();
341+ globalEngine = nullptr;
342+ }
343+}
259344
260345 void OffscreenQmlSurface::cleanup() {
261346 _canvas->makeCurrent();
@@ -294,6 +379,7 @@ void OffscreenQmlSurface::render() { · 32 unmodified lines
294379 GLuint texture = offscreenTextures.getNextTexture(_size);
295380 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
296381 glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
382+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
297383 _renderControl->render();
298384 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
299385 glBindTexture(GL_TEXTURE_2D, texture);
@@ -361,9 +447,8 @@ OffscreenQmlSurface::~OffscreenQmlSurface() { · 61 unmodified lines
361447
362448 _canvas->deleteLater();
363449 _rootItem->deleteLater();
364− _qmlComponent->deleteLater();
365− _qmlEngine->deleteLater();
366450 _quickWindow->deleteLater();
451+ releaseEngine();
367452 }
368453
369454 void OffscreenQmlSurface::onAboutToQuit() {
@@ -375,6 +460,8 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { · 5 unmodified lines
375460 qCDebug(glLogging) << "Building QML surface";
376461
377462 _renderControl = new QMyQuickRenderControl();
463+ connect(_renderControl, &QQuickRenderControl::renderRequested, this, [this] { _render = true; });
464+ connect(_renderControl, &QQuickRenderControl::sceneChanged, this, [this] { _render = _polish = true; });
378465
379466 QQuickWindow::setDefaultAlphaBuffer(true);
380467
@@ -385,7 +472,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { · 4 unmodified lines
385472 // so we wait until after its ctor to move object/context to this thread.
386473 _quickWindow = new QQuickWindow(_renderControl);
387474 _quickWindow->setColor(QColor(255, 255, 255, 0));
388− _quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
475+ _quickWindow->setClearBeforeRendering(false);
389476
390477 _renderControl->_renderWindow = _proxyWindow;
391478
@@ -398,32 +485,21 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { · 6 unmodified lines
398485 connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged);
399486
400487 // Create a QML engine.
401− _qmlEngine = new QQmlEngine;
402−
403− _qmlEngine->setNetworkAccessManagerFactory(new QmlNetworkAccessManagerFactory);
404−
405− auto importList = _qmlEngine->importPathList();
406− importList.insert(importList.begin(), PathUtils::resourcesPath());
407− _qmlEngine->setImportPathList(importList);
408− if (!_qmlEngine->incubationController()) {
409− _qmlEngine->setIncubationController(_quickWindow->incubationController());
410− }
411−
412− // FIXME
413− _glData = ::getGLContextData(); // Initialize JSON structure so that it can be filled in later and then used in QML.
414− _qmlEngine->rootContext()->setContextProperty("GL", _glData);
415− _qmlEngine->rootContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
416− _qmlComponent = new QQmlComponent(_qmlEngine);
488+ auto qmlEngine = acquireEngine(_quickWindow);
489+ _qmlContext = new QQmlContext(qmlEngine->rootContext());
417490
491+ _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
492+ _qmlContext->setContextProperty("eventBridge", this);
493+ _qmlContext->setContextProperty("webEntity", this);
418494
419− connect(_renderControl, &QQuickRenderControl::renderRequested, this, [this] { _render = true; });
420− connect(_renderControl, &QQuickRenderControl::sceneChanged, this, [this] { _render = _polish = true; });
495+ // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
496+ // Find a way to flag older scripts using this mechanism and wanr that this is deprecated
497+ _qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, _qmlContext));
421498
422499 if (!_canvas->makeCurrent()) {
423500 qWarning("Failed to make context current for QML Renderer");
424501 return;
425502 }
426− _glData = ::getGLContextData();
427503 _renderControl->initialize(_canvas->getContext());
428504
429505 // When Quick says there is a need to render, we will not render immediately. Instead,
@@ -433,11 +509,6 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { · 3 unmodified lines
433509 _updateTimer.setTimerType(Qt::PreciseTimer);
434510 _updateTimer.setInterval(MIN_TIMER_MS); // 5ms, Qt::PreciseTimer required
435511 _updateTimer.start();
436−
437− auto rootContext = getRootContext();
438− rootContext->setContextProperty("urlHandler", new UrlHandler());
439− rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath()));
440− rootContext->setContextProperty("pathToFonts", "../../");
441512 }
442513
443514 static uvec2 clampSize(const uvec2& size, uint32_t maxDimension) {
@@ -460,7 +531,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { · 16 unmodified lines
460531 return;
461532 }
462533
463− _qmlEngine->rootContext()->setContextProperty("surfaceSize", newSize);
534+ _qmlContext->setContextProperty("surfaceSize", newSize);
464535
465536 if (_rootItem) {
466537 _rootItem->setSize(newSize);
@@ -520,73 +591,82 @@ QQuickItem* OffscreenQmlSurface::getRootItem() { · 53 unmodified lines
520591 }
521592
522593 void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
523− _qmlEngine->setBaseUrl(baseUrl);
594+ _qmlContext->setBaseUrl(baseUrl);
524595 }
525596
526−QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) {
597+QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> f) {
527598 // Synchronous loading may take a while; restart the deadlock timer
528599 QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection);
529600
530− qDebug() << qmlSource;
531− _qmlComponent->loadUrl(qmlSource, QQmlComponent::PreferSynchronous);
601+ QQmlContext* targetContext = _qmlContext;
602+ if (_rootItem && createNewContext) {
603+ targetContext = new QQmlContext(targetContext);
604+ }
532605
533− if (_qmlComponent->isLoading()) {
534− connect(_qmlComponent, &QQmlComponent::statusChanged, this,
535− [this, f](QQmlComponent::Status){
536− finishQmlLoad(f);
537− });
606+ QUrl finalQmlSource = qmlSource;
607+ if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) {
608+ finalQmlSource = _qmlContext->resolvedUrl(qmlSource);
609+ }
610+
611+ auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
612+ if (qmlComponent->isLoading()) {
613+ connect(qmlComponent, &QQmlComponent::statusChanged, this,
614+ [this, qmlComponent, targetContext, f](QQmlComponent::Status) {
615+ finishQmlLoad(qmlComponent, targetContext, f);
616+ });
538617 return nullptr;
539618 }
540619
541− return finishQmlLoad(f);
620+ return finishQmlLoad(qmlComponent, targetContext, f);
621+}
622+
623+QObject* OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) {
624+ return load(qmlSource, true, f);
625+}
626+
627+QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) {
628+ return load(qmlSource, false, f);
542629 }
543630
544631 void OffscreenQmlSurface::clearCache() {
545− getRootContext()->engine()->clearComponentCache();
632+ _qmlContext->engine()->clearComponentCache();
546633 }
547634
548−QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) {
549− disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
550− if (_qmlComponent->isError()) {
551− QList<QQmlError> errorList = _qmlComponent->errors();
552− foreach(const QQmlError& error, errorList) {
553− qWarning() << error.url() << error.line() << error;
635+QObject* OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> f) {
636+ disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
637+ if (qmlComponent->isError()) {
638+ for (const auto& error : qmlComponent->errors()) {
639+ qCWarning(glLogging) << error.url() << error.line() << error;
554640 }
641+ qmlComponent->deleteLater();
555642 return nullptr;
556643 }
557644
558− // FIXME: Refactor with similar code in RenderableWebEntityItem
559− QString javaScriptToInject;
560− QFile webChannelFile(":qtwebchannel/qwebchannel.js");
561− QFile createGlobalEventBridgeFile(PathUtils::resourcesPath() + "/html/createGlobalEventBridge.js");
562− if (webChannelFile.open(QFile::ReadOnly | QFile::Text) &&
563− createGlobalEventBridgeFile.open(QFile::ReadOnly | QFile::Text)) {
564− QString webChannelStr = QTextStream(&webChannelFile).readAll();
565− QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll();
566− javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
567− } else {
568− qCWarning(glLogging) << "Unable to find qwebchannel.js or createGlobalEventBridge.js";
569− }
570645
571− QQmlContext* newContext = new QQmlContext(_qmlEngine, qApp);
572− QObject* newObject = _qmlComponent->beginCreate(newContext);
573− if (_qmlComponent->isError()) {
574− QList<QQmlError> errorList = _qmlComponent->errors();
575− foreach(const QQmlError& error, errorList)
646+ QObject* newObject = qmlComponent->beginCreate(qmlContext);
647+ if (qmlComponent->isError()) {
648+ for (const auto& error : qmlComponent->errors()) {
576649 qCWarning(glLogging) << error.url() << error.line() << error;
650+ }
577651 if (!_rootItem) {
578652 qFatal("Unable to finish loading QML root");
579653 }
654+ qmlComponent->deleteLater();
580655 return nullptr;
581656 }
582657
583− _qmlEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
584− newObject->setProperty("eventBridge", QVariant::fromValue(this));
658+ qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
659+ f(qmlContext, newObject);
585660
586− newContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject));
661+ QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
662+ if (qmlContext != _qmlContext && eventBridge && eventBridge != this) {
663+ // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
664+ // Find a way to flag older scripts using this mechanism and wanr that this is deprecated
665+ qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext));
666+ }
587667
588− f(newContext, newObject);
589− _qmlComponent->completeCreate();
668+ qmlComponent->completeCreate();
669+ qmlComponent->deleteLater();
590670
591671
592672 // All quick items should be focusable
@@ -736,7 +816,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even · 143 unmodified lines
736816 mouseEvent->screenPos(), mouseEvent->button(),
737817 mouseEvent->buttons(), mouseEvent->modifiers());
738818 if (event->type() == QEvent::MouseMove) {
739− _qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos);
819+ _qmlContext->setContextProperty("lastMousePosition", transformedPos);
740820 }
741821 mappedEvent.ignore();
742822 if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
@@ -763,9 +843,6 @@ void OffscreenQmlSurface::resume() { · 20 unmodified lines
763843 if (getRootItem()) {
764844 getRootItem()->setProperty("eventBridge", QVariant::fromValue(this));
765845 }
766− if (getRootContext()) {
767− getRootContext()->setContextProperty("webEntity", this);
768− }
769846 }
770847
771848 bool OffscreenQmlSurface::isPaused() const {
@@ -791,8 +868,8 @@ QSize OffscreenQmlSurface::size() const { · 19 unmodified lines
791868 return _quickWindow->geometry().size();
792869 }
793870
794−QQmlContext* OffscreenQmlSurface::getRootContext() {
795− return _qmlEngine->rootContext();
871+QQmlContext* OffscreenQmlSurface::getSurfaceContext() {
872+ return _qmlContext;
796873 }
797874
798875 Q_DECLARE_METATYPE(std::function<void()>);
libraries/gl/src/gl/OffscreenQmlSurface.h+5−5
@@ -48,6 +48,8 @@ public: · 47 unmodified lines
4848 void resize(const QSize& size, bool forceResize = false);
4949 QSize size() const;
5050
51+ Q_INVOKABLE QObject* load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
52+ Q_INVOKABLE QObject* loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
5153 Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
5254 Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) {
5355 return load(QUrl(qmlSourceFile), f);
@@ -73,7 +75,7 @@ public: · 19 unmodified lines
7375 QQuickItem* getRootItem();
7476 QQuickWindow* getWindow();
7577 QObject* getEventHandler();
76− QQmlContext* getRootContext();
78+ QQmlContext* getSurfaceContext();
7779
7880 QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget);
7981 bool eventFilter(QObject* originalDestination, QEvent* event) override;
@@ -118,7 +120,7 @@ protected: · 38 unmodified lines
118120 void setFocusText(bool newFocusText);
119121
120122 private:
121− QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
123+ QObject* finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> f);
122124 QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
123125 void setupFbo();
124126 bool allowNewFrame(uint8_t fps);
@@ -133,11 +135,9 @@ private slots: · 8 unmodified lines
133135 private:
134136 QQuickWindow* _quickWindow { nullptr };
135137 QMyQuickRenderControl* _renderControl{ nullptr };
136− QQmlEngine* _qmlEngine { nullptr };
137− QQmlComponent* _qmlComponent { nullptr };
138+ QQmlContext* _qmlContext { nullptr };
138139 QQuickItem* _rootItem { nullptr };
139140 OffscreenGLCanvas* _canvas { nullptr };
140− QJsonObject _glData;
141141
142142 QTimer _updateTimer;
143143 uint32_t _fbo { 0 };
libraries/gl/src/gl/OffscreenQmlSurfaceCache.cpp+0−1
@@ -49,7 +49,6 @@ QSharedPointer<OffscreenQmlSurface> OffscreenQmlSurfaceCache::buildSurface(const · 48 unmodified lines
4949 surface->create(currentContext);
5050 surface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
5151 surface->load(rootSource);
52− surface->getRootContext()->setContextProperty("ApplicationInterface", qApp);
5352 surface->resize(QSize(100, 100));
5453 currentContext->makeCurrent(currentSurface);
5554 return surface;
libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp+13−2
@@ -63,11 +63,17 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { · 62 unmodified lines
6363 int useScissor = batch._params[paramOffset + 0]._int;
6464
6565 GLuint glmask = 0;
66+ bool restoreStencilMask = false;
67+ uint8_t cacheStencilMask = 0xFF;
6668 if (masks & Framebuffer::BUFFER_STENCIL) {
6769 glClearStencil(stencil);
6870 glmask |= GL_STENCIL_BUFFER_BIT;
69− // TODO: we will probably need to also check the write mask of stencil like we do
70− // for depth buffer, but as would say a famous Fez owner "We'll cross that bridge when we come to it"
71+
72+ cacheStencilMask = _pipeline._stateCache.stencilActivation.getWriteMaskFront();
73+ if (cacheStencilMask != 0xFF) {
74+ restoreStencilMask = true;
75+ glStencilMask( 0xFF);
76+ }
7177 }
7278
7379 bool restoreDepthMask = false;
@@ -121,6 +127,11 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { · 47 unmodified lines
121127 glDisable(GL_SCISSOR_TEST);
122128 }
123129
130+ // Restore Stencil write mask
131+ if (restoreStencilMask) {
132+ glStencilMask(cacheStencilMask);
133+ }
134+
124135 // Restore write mask meaning turn back off
125136 if (restoreDepthMask) {
126137 glDepthMask(GL_FALSE);
libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp+4−0
@@ -211,6 +211,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { · 210 unmodified lines
211211 break;
212212 case gpu::NUINT32:
213213 case gpu::NINT32:
214+ case gpu::COMPRESSED:
214215 case gpu::NUM_TYPES: // quiet compiler
215216 Q_UNREACHABLE();
216217 }
@@ -484,6 +485,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E · 267 unmodified lines
484485 texel.internalFormat = GL_R8_SNORM;
485486 break;
486487 }
488+ case gpu::COMPRESSED:
487489 case gpu::NUM_TYPES: { // quiet compiler
488490 Q_UNREACHABLE();
489491 }
@@ -527,6 +529,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E · 37 unmodified lines
527529 texel.internalFormat = GL_DEPTH_COMPONENT24;
528530 break;
529531 }
532+ case gpu::COMPRESSED:
530533 case gpu::NUM_TYPES: { // quiet compiler
531534 Q_UNREACHABLE();
532535 }
@@ -641,6 +644,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E · 108 unmodified lines
641644 break;
642645 case gpu::NUINT32:
643646 case gpu::NINT32:
647+ case gpu::COMPRESSED:
644648 case gpu::NUM_TYPES: // quiet compiler
645649 Q_UNREACHABLE();
646650 }
libraries/gpu/src/gpu/DrawTransformVertexPosition.slv+28−0
@@ -0,0 +1,28 @@
1+<@include gpu/Config.slh@>
2+<$VERSION_HEADER$>
3+// Generated on <$_SCRIBE_DATE$>
4+//
5+// Draw and transform the fed vertex position with the standard MVP stack
6+// Output the clip position
7+//
8+// Created by Sam Gateau on 5/30/2017
9+// Copyright 2017 High Fidelity, Inc.
10+//
11+// Distributed under the Apache License, Version 2.0.
12+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
13+//
14+
15+<@include gpu/Transform.slh@>
16+
17+<$declareStandardTransform()$>
18+
19+layout(location = 0) in vec4 inPosition;
20+
21+out vec3 varWorldPos;
22+
23+void main(void) {
24+ // standard transform
25+ TransformCamera cam = getTransformCamera();
26+ TransformObject obj = getTransformObject();
27+ <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
28+}
libraries/gpu/src/gpu/DrawVertexPosition.slvrenamed from libraries/render-utils/src/hit_effect.slv+6−13
@@ -2,25 +2,18 @@ · 1 unmodified line
22 <$VERSION_HEADER$>
33 // Generated on <$_SCRIBE_DATE$>
44 //
5−// hit_effect.vert
6−// vertex shader
5+// Draw the fed vertex position, pass straight as clip pos
6+// Output the clip position
77 //
8−// Created by Eric Levin on 7/20/15.
9−// Copyright 2015 High Fidelity, Inc.
8+// Created by Sam Gateau on 5/30/2017
9+// Copyright 2017 High Fidelity, Inc.
1010 //
1111 // Distributed under the Apache License, Version 2.0.
1212 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
1313 //
1414
15−<@include gpu/Inputs.slh@>
16−
17−<@include gpu/Transform.slh@>
18−
19−<$declareStandardTransform()$>
20−
21−out vec2 varQuadPosition;
15+layout(location = 0) in vec4 inPosition;
2216
2317 void main(void) {
24− varQuadPosition = inPosition.xy;
2518 gl_Position = inPosition;
26−}
19+}
libraries/gpu/src/gpu/DrawWhite.slfrenamed from libraries/render-utils/src/drawOpaqueStencil.slf+6−4
@@ -2,15 +2,17 @@ · 1 unmodified line
22 <$VERSION_HEADER$>
33 // Generated on <$_SCRIBE_DATE$>
44 //
5−// drawOpaqueStencil.frag
6−// fragment shader
5+// Draw white
76 //
8−// Created by Sam Gateau on 9/29/15.
9−// Copyright 2015 High Fidelity, Inc.
7+// Created by Sam Gateau on 5/30/2017
8+// Copyright 2017 High Fidelity, Inc.
109 //
1110 // Distributed under the Apache License, Version 2.0.
1211 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
1312 //
1413
14+out vec4 outFragColor;
15+
1516 void main(void) {
17+ outFragColor = vec4(1.0);
1618 }
libraries/gpu/src/gpu/StandardShaderLib.cpp+38−2
@@ -16,6 +16,12 @@ · 15 unmodified lines
1616 #include "DrawTransformUnitQuad_vert.h"
1717 #include "DrawTexcoordRectTransformUnitQuad_vert.h"
1818 #include "DrawViewportQuadTransformTexcoord_vert.h"
19+#include "DrawVertexPosition_vert.h"
20+#include "DrawTransformVertexPosition_vert.h"
21+
22+const char DrawNada_frag[] = "void main(void) {}"; // DrawNada is really simple...
23+
24+#include "DrawWhite_frag.h"
1925 #include "DrawTexture_frag.h"
2026 #include "DrawTextureOpaque_frag.h"
2127 #include "DrawColoredTexture_frag.h"
@@ -26,6 +32,10 @@ ShaderPointer StandardShaderLib::_drawUnitQuadTexcoordVS; · 4 unmodified lines
2632 ShaderPointer StandardShaderLib::_drawTransformUnitQuadVS;
2733 ShaderPointer StandardShaderLib::_drawTexcoordRectTransformUnitQuadVS;
2834 ShaderPointer StandardShaderLib::_drawViewportQuadTransformTexcoordVS;
35+ShaderPointer StandardShaderLib::_drawVertexPositionVS;
36+ShaderPointer StandardShaderLib::_drawTransformVertexPositionVS;
37+ShaderPointer StandardShaderLib::_drawNadaPS;
38+ShaderPointer StandardShaderLib::_drawWhitePS;
2939 ShaderPointer StandardShaderLib::_drawTexturePS;
3040 ShaderPointer StandardShaderLib::_drawTextureOpaquePS;
3141 ShaderPointer StandardShaderLib::_drawColoredTexturePS;
@@ -85,6 +95,34 @@ ShaderPointer StandardShaderLib::getDrawViewportQuadTransformTexcoordVS() { · 53 unmodified lines
8595 return _drawViewportQuadTransformTexcoordVS;
8696 }
8797
98+ShaderPointer StandardShaderLib::getDrawVertexPositionVS() {
99+ if (!_drawVertexPositionVS) {
100+ _drawVertexPositionVS = gpu::Shader::createVertex(std::string(DrawVertexPosition_vert));
101+ }
102+ return _drawVertexPositionVS;
103+}
104+
105+ShaderPointer StandardShaderLib::getDrawTransformVertexPositionVS() {
106+ if (!_drawTransformVertexPositionVS) {
107+ _drawTransformVertexPositionVS = gpu::Shader::createVertex(std::string(DrawTransformVertexPosition_vert));
108+ }
109+ return _drawTransformVertexPositionVS;
110+}
111+
112+ShaderPointer StandardShaderLib::getDrawNadaPS() {
113+ if (!_drawNadaPS) {
114+ _drawNadaPS = gpu::Shader::createPixel(std::string(DrawNada_frag));
115+ }
116+ return _drawNadaPS;
117+}
118+
119+ShaderPointer StandardShaderLib::getDrawWhitePS() {
120+ if (!_drawWhitePS) {
121+ _drawWhitePS = gpu::Shader::createPixel(std::string(DrawWhite_frag));
122+ }
123+ return _drawWhitePS;
124+}
125+
88126 ShaderPointer StandardShaderLib::getDrawTexturePS() {
89127 if (!_drawTexturePS) {
90128 _drawTexturePS = gpu::Shader::createPixel(std::string(DrawTexture_frag));
@@ -99,8 +137,6 @@ ShaderPointer StandardShaderLib::getDrawTextureOpaquePS() { · 8 unmodified lines
99137 return _drawTextureOpaquePS;
100138 }
101139
102−
103−
104140 ShaderPointer StandardShaderLib::getDrawColoredTexturePS() {
105141 if (!_drawColoredTexturePS) {
106142 _drawColoredTexturePS = gpu::Shader::createPixel(std::string(DrawColoredTexture_frag));
libraries/gpu/src/gpu/StandardShaderLib.h+15−0
@@ -37,6 +37,15 @@ public: · 36 unmodified lines
3737 // Shader draws the unit quad in the full viewport clipPos = ([(-1,-1),(1,1)]) and transform the texcoord = [(0,0),(1,1)] by the model transform.
3838 static ShaderPointer getDrawViewportQuadTransformTexcoordVS();
3939
40+ // Shader draw the fed vertex position and transform it by the full model transform stack (Model, View, Proj).
41+ // simply output the world pos and the clip pos to the next stage
42+ static ShaderPointer getDrawVertexPositionVS();
43+ static ShaderPointer getDrawTransformVertexPositionVS();
44+
45+ // PShader does nothing, no really nothing, but still needed for defining a program triggering rasterization
46+ static ShaderPointer getDrawNadaPS();
47+
48+ static ShaderPointer getDrawWhitePS();
4049 static ShaderPointer getDrawTexturePS();
4150 static ShaderPointer getDrawTextureOpaquePS();
4251 static ShaderPointer getDrawColoredTexturePS();
@@ -51,6 +60,12 @@ protected: · 8 unmodified lines
5160 static ShaderPointer _drawTransformUnitQuadVS;
5261 static ShaderPointer _drawTexcoordRectTransformUnitQuadVS;
5362 static ShaderPointer _drawViewportQuadTransformTexcoordVS;
63+
64+ static ShaderPointer _drawVertexPositionVS;
65+ static ShaderPointer _drawTransformVertexPositionVS;
66+
67+ static ShaderPointer _drawNadaPS;
68+ static ShaderPointer _drawWhitePS;
5469 static ShaderPointer _drawTexturePS;
5570 static ShaderPointer _drawTextureOpaquePS;
5671 static ShaderPointer _drawColoredTexturePS;
libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h+0−2
@@ -70,8 +70,6 @@ public: · 69 unmodified lines
7070 bool isSupported() const override { return true; }
7171 const QString getName() const override { return NAME; }
7272
73− bool isHandController() const override { return false; }
74−
7573 void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); }
7674 void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
7775
libraries/input-plugins/src/input-plugins/TouchscreenDevice.h+0−2
@@ -39,8 +39,6 @@ public: · 38 unmodified lines
3939 virtual bool isSupported() const override;
4040 virtual const QString getName() const override { return NAME; }
4141
42− bool isHandController() const override { return false; }
43−
4442 virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); }
4543 virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
4644
libraries/ktx/src/khronos/KHR.h+2−0
@@ -211,6 +211,8 @@ namespace khronos { · 210 unmodified lines
211211
212212 template <uint32_t ALIGNMENT>
213213 inline uint32_t evalAlignedCompressedBlockCount(uint32_t value) {
214+ enum { val = ALIGNMENT && !(ALIGNMENT & (ALIGNMENT - 1)) };
215+ static_assert(val, "template parameter ALIGNMENT must be a power of 2");
214216 // FIXME add static assert that ALIGNMENT is a power of 2
215217 static uint32_t ALIGNMENT_REMAINDER = ALIGNMENT - 1;
216218 return (value + ALIGNMENT_REMAINDER) / ALIGNMENT;
libraries/ktx/src/ktx/Writer.cpp+1−1
@@ -229,7 +229,7 @@ namespace ktx { · 228 unmodified lines
229229 } else {
230230 Image::FaceBytes faceBytes(NUM_CUBEMAPFACES);
231231 auto faceSize = srcImages[l]._faceSize;
232− for (int face = 0; face < NUM_CUBEMAPFACES; face++) {
232+ for (uint32_t face = 0; face < NUM_CUBEMAPFACES; face++) {
233233 memcpy(currentPtr, srcImages[l]._faceBytes[face], faceSize);
234234 faceBytes[face] = currentPtr;
235235 currentPtr += faceSize;
libraries/model-networking/src/model-networking/KTXCache.cpp+14−0
@@ -11,14 +11,28 @@ · 10 unmodified lines
1111
1212 #include "KTXCache.h"
1313
14+#include <SettingHandle.h>
1415 #include <ktx/KTX.h>
1516
1617 using File = cache::File;
1718 using FilePointer = cache::FilePointer;
1819
20+// Whenever a change is made to the serialized format for the KTX cache that isn't backward compatible,
21+// this value should be incremented. This will force the KTX cache to be wiped
22+const int KTXCache::CURRENT_VERSION = 0x01;
23+const int KTXCache::INVALID_VERSION = 0x00;
24+const char* KTXCache::SETTING_VERSION_NAME = "hifi.ktx.cache_version";
25+
1926 KTXCache::KTXCache(const std::string& dir, const std::string& ext) :
2027 FileCache(dir, ext) {
2128 initialize();
29+
30+ Setting::Handle<int> cacheVersionHandle(SETTING_VERSION_NAME, INVALID_VERSION);
31+ auto cacheVersion = cacheVersionHandle.get();
32+ if (cacheVersion != CURRENT_VERSION) {
33+ wipe();
34+ cacheVersionHandle.set(CURRENT_VERSION);
35+ }
2236 }
2337
2438 KTXFilePointer KTXCache::writeFile(const char* data, Metadata&& metadata) {
libraries/model-networking/src/model-networking/KTXCache.h+6−0
@@ -27,6 +27,12 @@ class KTXCache : public cache::FileCache { · 26 unmodified lines
2727 Q_OBJECT
2828
2929 public:
30+ // Whenever a change is made to the serialized format for the KTX cache that isn't backward compatible,
31+ // this value should be incremented. This will force the KTX cache to be wiped
32+ static const int CURRENT_VERSION;
33+ static const int INVALID_VERSION;
34+ static const char* SETTING_VERSION_NAME;
35+
3036 KTXCache(const std::string& dir, const std::string& ext);
3137
3238 KTXFilePointer writeFile(const char* data, Metadata&& metadata);
libraries/model/src/model/Geometry.cpp+38−0
@@ -241,6 +241,42 @@ void Mesh::forEach(std::function<void(glm::vec3)> vertexFunc, · 240 unmodified lines
241241 }
242242 }
243243
244+MeshPointer Mesh::createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numIndices, const glm::vec3* vertices, const uint32_t* indices) {
245+ MeshPointer mesh;
246+ if (numVertices == 0) { return mesh; }
247+ if (numIndices < 3) { return mesh; }
248+
249+ mesh = std::make_shared<Mesh>();
250+
251+ // Vertex buffer
252+ mesh->setVertexBuffer(gpu::BufferView(new gpu::Buffer(numVertices * sizeof(glm::vec3), (gpu::Byte*) vertices), gpu::Element::VEC3F_XYZ));
253+
254+ // trim down the indices to shorts if possible
255+ if (numIndices < std::numeric_limits<uint16_t>::max()) {
256+ Indices16 shortIndicesVector;
257+ int16_t* shortIndices = nullptr;
258+ if (indices) {
259+ shortIndicesVector.resize(numIndices);
260+ for (uint32_t i = 0; i < numIndices; i++) {
261+ shortIndicesVector[i] = indices[i];
262+ }
263+ shortIndices = shortIndicesVector.data();
264+ }
265+
266+ mesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(numIndices * sizeof(uint16_t), (gpu::Byte*) shortIndices), gpu::Element::INDEX_UINT16));
267+ } else {
268+
269+ mesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(numIndices * sizeof(uint32_t), (gpu::Byte*) indices), gpu::Element::INDEX_INT32));
270+ }
271+
272+
273+ std::vector<model::Mesh::Part> parts;
274+ parts.push_back(model::Mesh::Part(0, numIndices, 0, model::Mesh::TRIANGLES));
275+ mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(model::Mesh::Part), (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
276+
277+ return mesh;
278+}
279+
244280
245281 Geometry::Geometry() {
246282 }
@@ -256,3 +292,5 @@ Geometry::~Geometry() { · 9 unmodified lines
256292 void Geometry::setMesh(const MeshPointer& mesh) {
257293 _mesh = mesh;
258294 }
295+
296+
libraries/model/src/model/Geometry.h+6−0
@@ -65,6 +65,9 @@ public: · 64 unmodified lines
6565 const gpu::BufferStream& getVertexStream() const { return _vertexStream; }
6666
6767 // Index Buffer
68+ using Indices16 = std::vector<int16_t>;
69+ using Indices32 = std::vector<int32_t>;
70+
6871 void setIndexBuffer(const BufferView& buffer);
6972 const BufferView& getIndexBuffer() const { return _indexBuffer; }
7073 size_t getNumIndices() const { return _indexBuffer.getNumElements(); }
@@ -127,6 +130,9 @@ public: · 56 unmodified lines
127130 std::function<void(glm::vec3)> normalFunc,
128131 std::function<void(uint32_t)> indexFunc);
129132
133+
134+ static MeshPointer createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numTriangles, const glm::vec3* vertices = nullptr, const uint32_t* indices = nullptr);
135+
130136 protected:
131137
132138 gpu::Stream::FormatPointer _vertexFormat;
libraries/model/src/model/Light.cpp+1−1
@@ -145,7 +145,7 @@ void Light::setAmbientSpherePreset(gpu::SphericalHarmonics::Preset preset) { · 144 unmodified lines
145145 _ambientSchemaBuffer.edit().ambientSphere.assignPreset(preset);
146146 }
147147
148−void Light::setAmbientMap(gpu::TexturePointer ambientMap) {
148+void Light::setAmbientMap(const gpu::TexturePointer& ambientMap) {
149149 _ambientMap = ambientMap;
150150 if (ambientMap) {
151151 setAmbientMapNumMips(_ambientMap->getNumMips());
libraries/model/src/model/Light.h+3−3
@@ -68,7 +68,8 @@ public: · 67 unmodified lines
6868
6969
7070 enum Type {
71− SUN = 0,
71+ AMBIENT = 0,
72+ SUN,
7273 POINT,
7374 SPOT,
7475
@@ -112,7 +113,6 @@ public: · 37 unmodified lines
112113 void setIntensity(float intensity);
113114
114115 bool isRanged() const { return (getType() == POINT) || (getType() == SPOT); }
115− bool hasAmbient() const { return (getType() == SUN); }
116116
117117 // FalloffRradius is the physical radius of the light sphere through which energy shines,
118118 // expressed in meters. It is used only to calculate the falloff curve of the light.
@@ -143,7 +143,7 @@ public: · 24 unmodified lines
143143 const gpu::SphericalHarmonics& getAmbientSphere() const { return _ambientSchemaBuffer->ambientSphere; }
144144 void setAmbientSpherePreset(gpu::SphericalHarmonics::Preset preset);
145145
146− void setAmbientMap(gpu::TexturePointer ambientMap);
146+ void setAmbientMap(const gpu::TexturePointer& ambientMap);
147147 gpu::TexturePointer getAmbientMap() const { return _ambientMap; }
148148
149149 void setAmbientMapNumMips(uint16_t numMips);
libraries/model/src/model/Skybox.cpp+1−1
@@ -97,7 +97,7 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky · 96 unmodified lines
9797 }
9898
9999 auto skyState = std::make_shared<gpu::State>();
100− skyState->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
100+ skyState->setStencilTest(true, 0xFF, gpu::State::StencilTest(1, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
101101
102102 thePipeline = gpu::Pipeline::create(skyShader, skyState);
103103 }
libraries/model/src/model/Skybox.h+4−0
@@ -25,6 +25,8 @@ typedef glm::vec3 Color; · 24 unmodified lines
2525
2626 class Skybox {
2727 public:
28+ typedef gpu::BufferView UniformBufferView;
29+
2830 Skybox();
2931 Skybox& operator= (const Skybox& skybox);
3032 virtual ~Skybox() {};
@@ -43,6 +45,8 @@ public: · 12 unmodified lines
4345
4446 static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox);
4547
48+ const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; }
49+
4650 protected:
4751 static const int SKYBOX_SKYMAP_SLOT { 0 };
4852 static const int SKYBOX_CONSTANTS_SLOT { 0 };
libraries/networking/CMakeLists.txt+1−1
@@ -1,5 +1,5 @@
11 set(TARGET_NAME networking)
2−setup_hifi_library(Network)
2+setup_hifi_library(Network WebEngine)
33 link_hifi_libraries(shared)
44
55 target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes")
libraries/networking/src/AccountManager.cpp+7−1
@@ -45,7 +45,6 @@ Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) · 44 unmodified lines
4545 Q_DECLARE_METATYPE(JSONCallbackParameters)
4646
4747 const QString ACCOUNTS_GROUP = "accounts";
48−static const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit();
4948
5049 JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod,
5150 QObject* errorCallbackReceiver, const QString& errorCallbackMethod,
@@ -201,6 +200,13 @@ void AccountManager::setAuthURL(const QUrl& authURL) { · 149 unmodified lines
201200 }
202201 }
203202
203+void AccountManager::setSessionID(const QUuid& sessionID) {
204+ if (_sessionID != sessionID) {
205+ qCDebug(networking) << "Metaverse session ID changed to" << uuidStringWithoutCurlyBraces(sessionID);
206+ _sessionID = sessionID;
207+ }
208+}
209+
204210 void AccountManager::sendRequest(const QString& path,
205211 AccountManagerAuth::Type authType,
206212 QNetworkAccessManager::Operation operation,
libraries/networking/src/AccountManager.h+2−1
@@ -52,6 +52,7 @@ namespace AccountManagerAuth { · 51 unmodified lines
5252 Q_DECLARE_METATYPE(AccountManagerAuth::Type);
5353
5454 const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization";
55+const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit();
5556
5657 using UserAgentGetter = std::function<QString()>;
5758
@@ -90,7 +91,7 @@ public: · 32 unmodified lines
9091 static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply);
9192
9293 QUuid getSessionID() const { return _sessionID; }
93− void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; }
94+ void setSessionID(const QUuid& sessionID);
9495
9596 void setTemporaryDomain(const QUuid& domainID, const QString& key);
9697 const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); }
libraries/networking/src/AssetRequest.cpp+7−2
@@ -15,11 +15,13 @@ · 14 unmodified lines
1515
1616 #include <QtCore/QThread>
1717
18+#include <StatTracker.h>
19+#include <Trace.h>
20+
1821 #include "AssetClient.h"
1922 #include "NetworkLogging.h"
2023 #include "NodeList.h"
2124 #include "ResourceCache.h"
22−#include <Trace.h>
2325
2426 static int requestID = 0;
2527
@@ -62,9 +64,12 @@ void AssetRequest::start() { · 36 unmodified lines
6264 _data = loadFromCache(getUrl());
6365 if (!_data.isNull()) {
6466 _error = NoError;
65−
67+
68+ _loadedFromCache = true;
69+
6670 _state = Finished;
6771 emit finished(this);
72+
6873 return;
6974 }
7075
libraries/networking/src/AssetRequest.h+3−0
@@ -52,6 +52,8 @@ public: · 51 unmodified lines
5252 QUrl getUrl() const { return ::getATPUrl(_hash); }
5353 QString getHash() const { return _hash; }
5454
55+ bool loadedFromCache() const { return _loadedFromCache; }
56+
5557 signals:
5658 void finished(AssetRequest* thisRequest);
5759 void progress(qint64 totalReceived, qint64 total);
@@ -66,6 +68,7 @@ private: · 8 unmodified lines
6668 int _numPendingRequests { 0 };
6769 MessageID _assetRequestID { INVALID_MESSAGE_ID };
6870 const ByteRange _byteRange;
71+ bool _loadedFromCache { false };
6972 };
7073
7174 #endif
libraries/networking/src/AssetResourceRequest.cpp+33−3
@@ -13,12 +13,14 @@ · 12 unmodified lines
1313
1414 #include <QtCore/QLoggingCategory>
1515
16+#include <Trace.h>
17+#include <Profile.h>
18+#include <StatTracker.h>
19+
1620 #include "AssetClient.h"
1721 #include "AssetUtils.h"
1822 #include "MappingRequest.h"
1923 #include "NetworkLogging.h"
20−#include <Trace.h>
21−#include <Profile.h>
2224
2325 static const int DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS = 5;
2426
@@ -48,6 +50,8 @@ bool AssetResourceRequest::urlIsAssetHash(const QUrl& url) { · 23 unmodified lines
4850 }
4951
5052 void AssetResourceRequest::doSend() {
53+ DependencyManager::get<StatTracker>()->incrementStat(STAT_ATP_REQUEST_STARTED);
54+
5155 // We'll either have a hash or an ATP path to a file (that maps to a hash)
5256 if (urlIsAssetHash(_url)) {
5357 // We've detected that this is a hash - simply use AssetClient to request that asset
@@ -65,11 +69,16 @@ void AssetResourceRequest::doSend() { · 11 unmodified lines
6569 }
6670
6771 void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
72+ auto statTracker = DependencyManager::get<StatTracker>();
73+ statTracker->incrementStat(STAT_ATP_MAPPING_REQUEST_STARTED);
74+
6875 auto assetClient = DependencyManager::get<AssetClient>();
6976 _assetMappingRequest = assetClient->createGetMappingRequest(path);
7077
7178 // make sure we'll hear about the result of the get mapping request
7279 connect(_assetMappingRequest, &GetMappingRequest::finished, this, [this, path](GetMappingRequest* request){
80+ auto statTracker = DependencyManager::get<StatTracker>();
81+
7382 Q_ASSERT(_state == InProgress);
7483 Q_ASSERT(request == _assetMappingRequest);
7584
@@ -80,6 +89,8 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) { · 4 unmodified lines
8089
8190 requestHash(request->getHash());
8291
92+ statTracker->incrementStat(STAT_ATP_MAPPING_REQUEST_SUCCESS);
93+
8394 break;
8495 default: {
8596 switch (request->getError()) {
@@ -100,6 +111,9 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) { · 14 unmodified lines
100111 _state = Finished;
101112 emit finished();
102113
114+ statTracker->incrementStat(STAT_ATP_MAPPING_REQUEST_FAILED);
115+ statTracker->incrementStat(STAT_ATP_REQUEST_FAILED);
116+
103117 break;
104118 }
105119 }
@@ -140,10 +154,26 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) { · 34 unmodified lines
140154 _result = Error;
141155 break;
142156 }
143−
157+
158+ auto statTracker = DependencyManager::get<StatTracker>();
159+
160+ if (_assetRequest->loadedFromCache()) {
161+ _loadedFromCache = true;
162+ }
163+
144164 _state = Finished;
145165 emit finished();
146166
167+ if (_result == Success) {
168+ statTracker->incrementStat(STAT_ATP_REQUEST_SUCCESS);
169+
170+ if (loadedFromCache()) {
171+ statTracker->incrementStat(STAT_ATP_REQUEST_CACHE);
172+ }
173+ } else {
174+ statTracker->incrementStat(STAT_ATP_REQUEST_FAILED);
175+ }
176+
147177 _assetRequest->deleteLater();
148178 _assetRequest = nullptr;
149179 });
libraries/networking/src/FileCache.cpp+146−64
@@ -11,52 +11,81 @@ · 10 unmodified lines
1111
1212 #include "FileCache.h"
1313
14−#include <cstdio>
15−#include <cassert>
16−#include <fstream>
14+
1715 #include <unordered_set>
16+#include <queue>
17+#include <cassert>
1818
19−#include <QDir>
20−#include <QSaveFile>
19+#include <QtCore/QDateTime>
20+#include <QtCore/QDir>
21+#include <QtCore/QSaveFile>
22+#include <QtCore/QStorageInfo>
2123
2224 #include <PathUtils.h>
25+#include <NumericalConstants.h>
2326
24−Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache", QtWarningMsg)
27+#ifdef Q_OS_WIN
28+#include <sys/utime.h>
29+#else
30+#include <utime.h>
31+#endif
2532
33+#ifdef NDEBUG
34+Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache", QtWarningMsg)
35+#else
36+Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache")
37+#endif
2638 using namespace cache;
2739
28−static const std::string MANIFEST_NAME = "manifest";
40+static const char DIR_SEP = '/';
41+static const char EXT_SEP = '.';
42+
43+const size_t FileCache::DEFAULT_MAX_SIZE { GB_TO_BYTES(5) };
44+const size_t FileCache::MAX_MAX_SIZE { GB_TO_BYTES(100) };
45+const size_t FileCache::DEFAULT_MIN_FREE_STORAGE_SPACE { GB_TO_BYTES(1) };
46+
2947
30−static const size_t BYTES_PER_MEGABYTES = 1024 * 1024;
31−static const size_t BYTES_PER_GIGABYTES = 1024 * BYTES_PER_MEGABYTES;
32−const size_t FileCache::DEFAULT_UNUSED_MAX_SIZE = 5 * BYTES_PER_GIGABYTES; // 5GB
33−const size_t FileCache::MAX_UNUSED_MAX_SIZE = 100 * BYTES_PER_GIGABYTES; // 100GB
34−const size_t FileCache::DEFAULT_OFFLINE_MAX_SIZE = 2 * BYTES_PER_GIGABYTES; // 2GB
48+std::string getCacheName(const std::string& dirname_str) {
49+ QString dirname { dirname_str.c_str() };
50+ QDir dir(dirname);
51+ if (!dir.isAbsolute()) {
52+ return dirname_str;
53+ }
54+ return dir.dirName().toStdString();
55+}
3556
36−void FileCache::setUnusedFileCacheSize(size_t unusedFilesMaxSize) {
37− _unusedFilesMaxSize = std::min(unusedFilesMaxSize, MAX_UNUSED_MAX_SIZE);
38− reserve(0);
57+std::string getCachePath(const std::string& dirname_str) {
58+ QString dirname { dirname_str.c_str() };
59+ QDir dir(dirname);
60+ if (dir.isAbsolute()) {
61+ return dirname_str;
62+ }
63+ return PathUtils::getAppLocalDataFilePath(dirname).toStdString();
64+}
65+
66+void FileCache::setMinFreeSize(size_t size) {
67+ _minFreeSpaceSize = size;
68+ clean();
3969 emit dirty();
4070 }
4171
42−void FileCache::setOfflineFileCacheSize(size_t offlineFilesMaxSize) {
43− _offlineFilesMaxSize = std::min(offlineFilesMaxSize, MAX_UNUSED_MAX_SIZE);
72+void FileCache::setMaxSize(size_t maxSize) {
73+ _maxSize = std::min(maxSize, MAX_MAX_SIZE);
74+ clean();
75+ emit dirty();
4476 }
4577
4678 FileCache::FileCache(const std::string& dirname, const std::string& ext, QObject* parent) :
4779 QObject(parent),
4880 _ext(ext),
49− _dirname(dirname),
50− _dirpath(PathUtils::getAppLocalDataFilePath(dirname.c_str()).toStdString()) {}
81+ _dirname(getCacheName(dirname)),
82+ _dirpath(getCachePath(dirname)) {
83+}
5184
5285 FileCache::~FileCache() {
5386 clear();
5487 }
5588
56−void fileDeleter(File* file) {
57− file->deleter();
58−}
59−
6089 void FileCache::initialize() {
6190 QDir dir(_dirpath.c_str());
6291
@@ -84,7 +113,7 @@ void FileCache::initialize() { · 21 unmodified lines
84113 }
85114
86115 FilePointer FileCache::addFile(Metadata&& metadata, const std::string& filepath) {
87− FilePointer file(createFile(std::move(metadata), filepath).release(), &fileDeleter);
116+ FilePointer file(createFile(std::move(metadata), filepath).release(), std::mem_fn(&File::deleter));
88117 if (file) {
89118 _numTotalFiles += 1;
90119 _totalFilesSize += file->getLength();
@@ -141,6 +170,7 @@ FilePointer FileCache::getFile(const Key& key) { · 50 unmodified lines
141170 if (it != _files.cend()) {
142171 file = it->second.lock();
143172 if (file) {
173+ file->touch();
144174 // if it exists, it is active - remove it from the cache
145175 removeUnusedFile(file);
146176 qCDebug(file_cache, "[%s] Found %s", _dirname.c_str(), key.c_str());
@@ -155,82 +185,127 @@ FilePointer FileCache::getFile(const Key& key) { · 8 unmodified lines
155185 }
156186
157187 std::string FileCache::getFilepath(const Key& key) {
158− return _dirpath + '/' + key + '.' + _ext;
188+ return _dirpath + DIR_SEP + key + EXT_SEP + _ext;
159189 }
160190
161−void FileCache::addUnusedFile(const FilePointer file) {
191+void FileCache::addUnusedFile(const FilePointer& file) {
162192 {
163193 Lock lock(_filesMutex);
164194 _files[file->getKey()] = file;
165195 }
166196
167− reserve(file->getLength());
168− file->_LRUKey = ++_lastLRUKey;
169−
170197 {
171198 Lock lock(_unusedFilesMutex);
172− _unusedFiles.insert({ file->_LRUKey, file });
199+ _unusedFiles.insert(file);
173200 _numUnusedFiles += 1;
174201 _unusedFilesSize += file->getLength();
175202 }
203+ clean();
176204
177205 emit dirty();
178206 }
179207
180−void FileCache::removeUnusedFile(const FilePointer file) {
208+void FileCache::removeUnusedFile(const FilePointer& file) {
181209 Lock lock(_unusedFilesMutex);
182− const auto it = _unusedFiles.find(file->_LRUKey);
183− if (it != _unusedFiles.cend()) {
184− _unusedFiles.erase(it);
210+ if (_unusedFiles.erase(file)) {
185211 _numUnusedFiles -= 1;
186212 _unusedFilesSize -= file->getLength();
187213 }
188214 }
189215
190−void FileCache::reserve(size_t length) {
191− Lock unusedLock(_unusedFilesMutex);
192− while (!_unusedFiles.empty() &&
193− _unusedFilesSize + length > _unusedFilesMaxSize) {
194− auto it = _unusedFiles.begin();
195− auto file = it->second;
196− auto length = file->getLength();
216+size_t FileCache::getOverbudgetAmount() const {
217+ size_t result = 0;
218+
219+ size_t currentFreeSpace = QStorageInfo(_dirpath.c_str()).bytesFree();
220+ if (_minFreeSpaceSize > currentFreeSpace) {
221+ result = _minFreeSpaceSize - currentFreeSpace;
222+ }
197223
198− unusedLock.unlock();
199− {
200− file->_cache = nullptr;
201− Lock lock(_filesMutex);
202− _files.erase(file->getKey());
224+ if (_totalFilesSize > _maxSize) {
225+ result = std::max(_totalFilesSize - _maxSize, result);
226+ }
227+
228+ return result;
229+}
230+
231+namespace cache {
232+ struct FilePointerComparator {
233+ bool operator()(const FilePointer& a, const FilePointer& b) {
234+ return a->_modified > b->_modified;
203235 }
204− unusedLock.lock();
236+ };
237+}
205238
206− _unusedFiles.erase(it);
207− _numTotalFiles -= 1;
208− _numUnusedFiles -= 1;
209− _totalFilesSize -= length;
210− _unusedFilesSize -= length;
239+void FileCache::eject(const FilePointer& file) {
240+ file->_cache = nullptr;
241+ const auto& length = file->getLength();
242+ const auto& key = file->getKey();
243+
244+ {
245+ Lock lock(_filesMutex);
246+ if (0 != _files.erase(key)) {
247+ _numTotalFiles -= 1;
248+ _totalFilesSize -= length;
249+ }
250+ }
251+
252+ {
253+ Lock unusedLock(_unusedFilesMutex);
254+ if (0 != _unusedFiles.erase(file)) {
255+ _numUnusedFiles -= 1;
256+ _unusedFilesSize -= length;
257+ }
258+ }
259+}
260+
261+void FileCache::clean() {
262+ size_t overbudgetAmount = getOverbudgetAmount();
263+
264+ // Avoid sorting the unused files by LRU if we're not over budget / under free space
265+ if (0 == overbudgetAmount) {
266+ return;
267+ }
268+
269+ Lock unusedLock(_unusedFilesMutex);
270+ using Queue = std::priority_queue<FilePointer, std::vector<FilePointer>, FilePointerComparator>;
271+ Queue queue;
272+ for (const auto& file : _unusedFiles) {
273+ queue.push(file);
274+ }
275+
276+ while (!queue.empty() && overbudgetAmount > 0) {
277+ auto file = queue.top();
278+ queue.pop();
279+ eject(file);
280+ auto length = file->getLength();
281+ overbudgetAmount -= std::min(length, overbudgetAmount);
282+ }
283+}
284+
285+void FileCache::wipe() {
286+ Lock unusedFilesLock(_unusedFilesMutex);
287+ while (!_unusedFiles.empty()) {
288+ eject(*_unusedFiles.begin());
211289 }
212290 }
213291
214292 void FileCache::clear() {
293+ // Eliminate any overbudget files
294+ clean();
295+
296+ // Mark everything remaining as persisted
215297 Lock unusedFilesLock(_unusedFilesMutex);
216− for (const auto& pair : _unusedFiles) {
217− auto& file = pair.second;
298+ for (auto& file : _unusedFiles) {
299+ file->_shouldPersist = true;
218300 file->_cache = nullptr;
219−
220− if (_totalFilesSize > _offlineFilesMaxSize) {
221− _totalFilesSize -= file->getLength();
222− } else {
223− file->_shouldPersist = true;
224− qCDebug(file_cache, "[%s] Persisting %s", _dirname.c_str(), file->getKey().c_str());
225− }
301+ qCDebug(file_cache, "[%s] Persisting %s", _dirname.c_str(), file->getKey().c_str());
226302 }
227303 _unusedFiles.clear();
228304 }
229305
230306 void File::deleter() {
231307 if (_cache) {
232− FilePointer self(this, &fileDeleter);
233− _cache->addUnusedFile(self);
308+ _cache->addUnusedFile(FilePointer(this, std::mem_fn(&File::deleter)));
234309 } else {
235310 deleteLater();
236311 }
@@ -239,7 +314,9 @@ void File::deleter() { · 2 unmodified lines
239314 File::File(Metadata&& metadata, const std::string& filepath) :
240315 _key(std::move(metadata.key)),
241316 _length(metadata.length),
242− _filepath(filepath) {}
317+ _filepath(filepath),
318+ _modified(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch()) {
319+}
243320
244321 File::~File() {
245322 QFile file(getFilepath().c_str());
@@ -248,3 +325,8 @@ File::~File() { · 2 unmodified lines
248325 file.remove();
249326 }
250327 }
328+
329+void File::touch() {
330+ utime(_filepath.c_str(), nullptr);
331+ _modified = std::max<int64_t>(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch(), _modified);
332+}
libraries/networking/src/FileCache.h+42−24
@@ -14,6 +14,7 @@ · 13 unmodified lines
1414 #include <memory>
1515 #include <cstddef>
1616 #include <map>
17+#include <unordered_set>
1718 #include <mutex>
1819 #include <string>
1920 #include <unordered_map>
@@ -35,24 +36,31 @@ class FileCache : public QObject { · 15 unmodified lines
3536 Q_PROPERTY(size_t sizeTotal READ getSizeTotalFiles NOTIFY dirty)
3637 Q_PROPERTY(size_t sizeCached READ getSizeCachedFiles NOTIFY dirty)
3738
38− static const size_t DEFAULT_UNUSED_MAX_SIZE;
39− static const size_t MAX_UNUSED_MAX_SIZE;
40− static const size_t DEFAULT_OFFLINE_MAX_SIZE;
39+ static const size_t DEFAULT_MAX_SIZE;
40+ static const size_t MAX_MAX_SIZE;
41+ static const size_t DEFAULT_MIN_FREE_STORAGE_SPACE;
4142
4243 public:
44+ // You can initialize the FileCache with a directory name (ex.: "temp_jpgs") that will be relative to the application local data, OR with a full path
45+ // The file cache will ignore any file that doesn't match the extension provided
46+ FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr);
47+ virtual ~FileCache();
48+
49+ // Remove all unlocked items from the cache
50+ void wipe();
51+
4352 size_t getNumTotalFiles() const { return _numTotalFiles; }
4453 size_t getNumCachedFiles() const { return _numUnusedFiles; }
4554 size_t getSizeTotalFiles() const { return _totalFilesSize; }
4655 size_t getSizeCachedFiles() const { return _unusedFilesSize; }
4756
48− void setUnusedFileCacheSize(size_t unusedFilesMaxSize);
49− size_t getUnusedFileCacheSize() const { return _unusedFilesSize; }
57+ // Set the maximum amount of disk space to use on disk
58+ void setMaxSize(size_t maxCacheSize);
5059
51− void setOfflineFileCacheSize(size_t offlineFilesMaxSize);
52−
53− // initialize FileCache with a directory name (not a path, ex.: "temp_jpgs") and an ext (ex.: "jpg")
54− FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr);
55− virtual ~FileCache();
60+ // Set the minumum amount of free disk space to retain. This supercedes the max size,
61+ // so if the cache is consuming all but 500 MB of the drive, unused entries will be ejected
62+ // to free up more space, regardless of the cache max size
63+ void setMinFreeSize(size_t size);
5664
5765 using Key = std::string;
5866 struct Metadata {
@@ -76,10 +84,11 @@ public: · 17 unmodified lines
7684 signals:
7785 void dirty();
7886
79−protected:
87+public:
8088 /// must be called after construction to create the cache on the fs and restore persisted files
8189 void initialize();
8290
91+ // Add file to the cache and return the cache entry.
8392 FilePointer writeFile(const char* data, Metadata&& metadata, bool overwrite = false);
8493 FilePointer getFile(const Key& key);
8594
@@ -89,17 +98,28 @@ protected: · 3 unmodified lines
8998 private:
9099 using Mutex = std::recursive_mutex;
91100 using Lock = std::unique_lock<Mutex>;
101+ using Map = std::unordered_map<Key, std::weak_ptr<File>>;
102+ using Set = std::unordered_set<FilePointer>;
103+ using KeySet = std::unordered_set<Key>;
92104
93105 friend class File;
94106
95107 std::string getFilepath(const Key& key);
96108
97109 FilePointer addFile(Metadata&& metadata, const std::string& filepath);
98− void addUnusedFile(const FilePointer file);
99− void removeUnusedFile(const FilePointer file);
100− void reserve(size_t length);
110+ void addUnusedFile(const FilePointer& file);
111+ void removeUnusedFile(const FilePointer& file);
112+ void clean();
101113 void clear();
114+ // Remove a file from the cache
115+ void eject(const FilePointer& file);
102116
117+ size_t getOverbudgetAmount() const;
118+
119+ // FIXME it might be desirable to have the min free space variable be static so it can be
120+ // shared among multiple instances of FileCache
121+ std::atomic<size_t> _minFreeSpaceSize { DEFAULT_MIN_FREE_STORAGE_SPACE };
122+ std::atomic<size_t> _maxSize { DEFAULT_MAX_SIZE };
103123 std::atomic<size_t> _numTotalFiles { 0 };
104124 std::atomic<size_t> _numUnusedFiles { 0 };
105125 std::atomic<size_t> _totalFilesSize { 0 };
@@ -110,15 +130,11 @@ private: · 4 unmodified lines
110130 std::string _dirpath;
111131 bool _initialized { false };
112132
113− std::unordered_map<Key, std::weak_ptr<File>> _files;
133+ Map _files;
114134 Mutex _filesMutex;
115135
116− std::map<int, FilePointer> _unusedFiles;
136+ Set _unusedFiles;
117137 Mutex _unusedFilesMutex;
118− size_t _unusedFilesMaxSize { DEFAULT_UNUSED_MAX_SIZE };
119− int _lastLRUKey { 0 };
120−
121− size_t _offlineFilesMaxSize { DEFAULT_OFFLINE_MAX_SIZE };
122138 };
123139
124140 class File : public QObject {
@@ -128,8 +144,8 @@ public: · 3 unmodified lines
128144 using Key = FileCache::Key;
129145 using Metadata = FileCache::Metadata;
130146
131− Key getKey() const { return _key; }
132− size_t getLength() const { return _length; }
147+ const Key& getKey() const { return _key; }
148+ const size_t& getLength() const { return _length; }
133149 std::string getFilepath() const { return _filepath; }
134150
135151 virtual ~File();
@@ -142,13 +158,15 @@ protected: · 6 unmodified lines
142158
143159 private:
144160 friend class FileCache;
161+ friend struct FilePointerComparator;
145162
146163 const Key _key;
147164 const size_t _length;
148165 const std::string _filepath;
149166
150− FileCache* _cache;
151− int _LRUKey { 0 };
167+ void touch();
168+ FileCache* _cache { nullptr };
169+ int64_t _modified { 0 };
152170
153171 bool _shouldPersist { false };
154172 };
libraries/networking/src/FileResourceRequest.cpp+11−0
@@ -15,7 +15,12 @@ · 14 unmodified lines
1515
1616 #include <QFile>
1717
18+#include <StatTracker.h>
19+
1820 void FileResourceRequest::doSend() {
21+ auto statTracker = DependencyManager::get<StatTracker>();
22+ statTracker->incrementStat(STAT_FILE_REQUEST_STARTED);
23+
1924 QString filename = _url.toLocalFile();
2025
2126 // sometimes on windows, we see the toLocalFile() return null,
@@ -60,4 +65,10 @@ void FileResourceRequest::doSend() { · 38 unmodified lines
6065
6166 _state = Finished;
6267 emit finished();
68+
69+ if (_result == ResourceRequest::Success) {
70+ statTracker->incrementStat(STAT_FILE_REQUEST_SUCCESS);
71+ } else {
72+ statTracker->incrementStat(STAT_FILE_REQUEST_FAILED);
73+ }
6374 }
libraries/networking/src/FileTypeProfile.cpprenamed from interface/src/networking/FileTypeProfile.cpp+0−0

File renamed without changes.

libraries/networking/src/FileTypeProfile.hrenamed from interface/src/networking/FileTypeProfile.h+0−0

File renamed without changes.

libraries/networking/src/FileTypeRequestInterceptor.cpprenamed from interface/src/networking/FileTypeRequestInterceptor.cpp+0−0

File renamed without changes.

libraries/networking/src/FileTypeRequestInterceptor.hrenamed from interface/src/networking/FileTypeRequestInterceptor.h+0−0

File renamed without changes.

libraries/networking/src/HFTabletWebEngineProfile.cpprenamed from interface/src/networking/HFTabletWebEngineProfile.cpp+1−0
@@ -19,6 +19,7 @@ HFTabletWebEngineProfile::HFTabletWebEngineProfile(QObject* parent) : QQuickWebE · 18 unmodified lines
1919 static const QString WEB_ENGINE_USER_AGENT = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36";
2020
2121 setHttpUserAgent(WEB_ENGINE_USER_AGENT);
22+ setStorageName(QML_WEB_ENGINE_NAME);
2223
2324 auto requestInterceptor = new HFTabletWebEngineRequestInterceptor(this);
2425 setRequestInterceptor(requestInterceptor);
libraries/networking/src/HFTabletWebEngineProfile.hrenamed from interface/src/networking/HFTabletWebEngineProfile.h+0−0

File renamed without changes.

libraries/networking/src/HFTabletWebEngineRequestInterceptor.cpprenamed from interface/src/networking/HFTabletWebEngineRequestInterceptor.cpp+1−1
@@ -11,7 +11,7 @@ · 10 unmodified lines
1111
1212 #include "HFTabletWebEngineRequestInterceptor.h"
1313 #include <QtCore/QDebug>
14−#include <AccountManager.h>
14+#include "AccountManager.h"
1515
1616 bool isTabletAuthableHighFidelityURL(const QUrl& url) {
1717 static const QStringList HF_HOSTS = {
libraries/networking/src/HFTabletWebEngineRequestInterceptor.hrenamed from interface/src/networking/HFTabletWebEngineRequestInterceptor.h+0−0

File renamed without changes.

libraries/networking/src/HFWebEngineProfile.cpprenamed from interface/src/networking/HFWebEngineProfile.cpp+1−0
@@ -20,6 +20,7 @@ HFWebEngineProfile::HFWebEngineProfile(QObject* parent) : · 19 unmodified lines
2020 {
2121 static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)";
2222 setHttpUserAgent(WEB_ENGINE_USER_AGENT);
23+ setStorageName(QML_WEB_ENGINE_STORAGE_NAME);
2324
2425 // we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user
2526 auto requestInterceptor = new HFWebEngineRequestInterceptor(this);
libraries/networking/src/HFWebEngineProfile.hrenamed from interface/src/networking/HFWebEngineProfile.h+0−0

File renamed without changes.

libraries/networking/src/HFWebEngineRequestInterceptor.cpprenamed from interface/src/networking/HFWebEngineRequestInterceptor.cpp+1−1
@@ -13,7 +13,7 @@ · 12 unmodified lines
1313
1414 #include <QtCore/QDebug>
1515
16−#include <AccountManager.h>
16+#include "AccountManager.h"
1717 #include "RequestFilters.h"
1818
1919 void HFWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) {
libraries/networking/src/HFWebEngineRequestInterceptor.hrenamed from interface/src/networking/HFWebEngineRequestInterceptor.h+0−0

File renamed without changes.

libraries/networking/src/HTTPResourceRequest.cpp+16−0
@@ -17,6 +17,7 @@ · 16 unmodified lines
1717 #include <QMetaEnum>
1818
1919 #include <SharedUtil.h>
20+#include <StatTracker.h>
2021
2122 #include "NetworkAccessManager.h"
2223 #include "NetworkLogging.h"
@@ -49,6 +50,8 @@ void HTTPResourceRequest::cleanupTimer() { · 26 unmodified lines
4950 }
5051
5152 void HTTPResourceRequest::doSend() {
53+ DependencyManager::get<StatTracker>()->incrementStat(STAT_HTTP_REQUEST_STARTED);
54+
5255 QNetworkRequest networkRequest(_url);
5356 networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
5457 networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
@@ -178,6 +181,17 @@ void HTTPResourceRequest::onRequestFinished() { · 123 unmodified lines
178181
179182 _state = Finished;
180183 emit finished();
184+
185+ auto statTracker = DependencyManager::get<StatTracker>();
186+ if (_result == Success) {
187+ statTracker->incrementStat(STAT_HTTP_REQUEST_SUCCESS);
188+
189+ if (loadedFromCache()) {
190+ statTracker->incrementStat(STAT_HTTP_REQUEST_CACHE);
191+ }
192+ } else {
193+ statTracker->incrementStat(STAT_HTTP_REQUEST_FAILED);
194+ }
181195 }
182196
183197 void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
@@ -202,4 +216,6 @@ void HTTPResourceRequest::onTimeout() { · 18 unmodified lines
202216 _result = Timeout;
203217 _state = Finished;
204218 emit finished();
219+
220+ DependencyManager::get<StatTracker>()->incrementStat(STAT_HTTP_REQUEST_FAILED);
205221 }
libraries/networking/src/LimitedNodeList.cpp+2−4
@@ -22,13 +22,12 @@ · 21 unmodified lines
2222 #include <QtCore/QUrl>
2323 #include <QtNetwork/QHostInfo>
2424
25−#include <tbb/parallel_for.h>
26−
2725 #include <LogHandler.h>
2826 #include <shared/NetworkUtils.h>
2927 #include <NumericalConstants.h>
3028 #include <SettingHandle.h>
3129 #include <SharedUtil.h>
30+#include <StatTracker.h>
3231 #include <UUID.h>
3332
3433 #include "AccountManager.h"
@@ -37,7 +36,6 @@ · 2 unmodified lines
3736 #include "HifiSockAddr.h"
3837 #include "NetworkLogging.h"
3938 #include "udt/Packet.h"
40−#include <Trace.h>
4139
4240 static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0);
4341
@@ -1044,10 +1042,10 @@ void LimitedNodeList::setLocalSocket(const HifiSockAddr& sockAddr) { · 1000 unmodified lines
10441042 qCInfo(networking) << "Local socket is" << sockAddr;
10451043 } else {
10461044 qCInfo(networking) << "Local socket has changed from" << _localSockAddr << "to" << sockAddr;
1045+ DependencyManager::get<StatTracker>()->incrementStat(LOCAL_SOCKET_CHANGE_STAT);
10471046 }
10481047
10491048 _localSockAddr = sockAddr;
1050−
10511049 emit localSockAddrChanged(_localSockAddr);
10521050 }
10531051 }
libraries/networking/src/LimitedNodeList.h+4−3
@@ -31,7 +31,7 @@ · 30 unmodified lines
3131 #include <QtNetwork/QUdpSocket>
3232 #include <QtNetwork/QHostAddress>
3333
34−#include <tbb/concurrent_unordered_map.h>
34+#include <TBBHelpers.h>
3535
3636 #include <DependencyManager.h>
3737 #include <SharedUtil.h>
@@ -66,9 +66,10 @@ const QHostAddress DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME = QHostAddress::Lo · 28 unmodified lines
6666
6767 const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username";
6868
69−using namespace tbb;
69+const QString LOCAL_SOCKET_CHANGE_STAT = "LocalSocketChanges";
70+
7071 typedef std::pair<QUuid, SharedNodePointer> UUIDNodePair;
71−typedef concurrent_unordered_map<QUuid, SharedNodePointer, UUIDHasher> NodeHash;
72+typedef tbb::concurrent_unordered_map<QUuid, SharedNodePointer, UUIDHasher> NodeHash;
7273
7374 typedef quint8 PingType_t;
7475 namespace PingType {
libraries/networking/src/MessagesClient.cpp+6−6
@@ -16,6 +16,8 @@ · 15 unmodified lines
1616 #include <QtCore/QBuffer>
1717 #include <QtCore/QThread>
1818
19+#include <ThreadHelpers.h>
20+
1921 #include "NetworkLogging.h"
2022 #include "NodeList.h"
2123 #include "PacketReceiver.h"
@@ -30,12 +32,6 @@ MessagesClient::MessagesClient() { · 8 unmodified lines
3032 connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated);
3133 }
3234
33−void MessagesClient::init() {
34− if (QThread::currentThread() != thread()) {
35− QMetaObject::invokeMethod(this, "init", Qt::BlockingQueuedConnection);
36− }
37−}
38−
3935 void MessagesClient::decodeMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, QString& channel,
4036 bool& isText, QString& message, QByteArray& data, QUuid& senderID) {
4137 quint16 channelLength;
@@ -185,3 +181,7 @@ void MessagesClient::handleNodeActivated(SharedNodePointer node) { · 143 unmodified lines
185181 }
186182 }
187183 }
184+
185+void MessagesClient::startThread() {
186+ moveToNewNamedThread(this, "Messages Client Thread");
187+}
libraries/networking/src/MessagesClient.h+1−1
@@ -28,7 +28,7 @@ class MessagesClient : public QObject, public Dependency { · 27 unmodified lines
2828 public:
2929 MessagesClient();
3030
31− Q_INVOKABLE void init();
31+ void startThread();
3232
3333 Q_INVOKABLE void sendMessage(QString channel, QString message, bool localOnly = false);
3434 Q_INVOKABLE void sendLocalMessage(QString channel, QString message);
libraries/networking/src/Node.h+1−1
@@ -24,7 +24,7 @@ · 23 unmodified lines
2424 #include <QReadLocker>
2525 #include <UUIDHasher.h>
2626
27−#include <tbb/concurrent_unordered_set.h>
27+#include <TBBHelpers.h>
2828
2929 #include "HifiSockAddr.h"
3030 #include "NetworkPeer.h"
libraries/networking/src/NodeList.cpp+6−0
@@ -20,6 +20,7 @@ · 19 unmodified lines
2020 #include <QtNetwork/QHostInfo>
2121 #include <QtNetwork/QNetworkInterface>
2222
23+#include <ThreadHelpers.h>
2324 #include <LogHandler.h>
2425 #include <UUID.h>
2526
@@ -1115,3 +1116,8 @@ void NodeList::setRequestsDomainListData(bool isRequesting) { · 1089 unmodified lines
11151116 });
11161117 _requestsDomainListData = isRequesting;
11171118 }
1119+
1120+
1121+void NodeList::startThread() {
1122+ moveToNewNamedThread(this, "NodeList Thread", QThread::TimeCriticalPriority);
1123+}
libraries/networking/src/NodeList.h+2−1
@@ -20,7 +20,7 @@ · 19 unmodified lines
2020 #include <unistd.h> // not on windows, not needed for mac or windows
2121 #endif
2222
23−#include <tbb/concurrent_unordered_set.h>
23+#include <TBBHelpers.h>
2424
2525 #include <QtCore/QElapsedTimer>
2626 #include <QtCore/QMutex>
@@ -52,6 +52,7 @@ class NodeList : public LimitedNodeList { · 25 unmodified lines
5252 SINGLETON_DEPENDENCY
5353
5454 public:
55+ void startThread();
5556 NodeType_t getOwnerType() const { return _ownerType.load(); }
5657 void setOwnerType(NodeType_t ownerType) { _ownerType.store(ownerType); }
5758
libraries/networking/src/RequestFilters.cpprenamed from interface/src/networking/RequestFilters.cpp+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
1414
1515 #include <QtCore/QDebug>
1616
17−#include <AccountManager.h>
17+#include "AccountManager.h"
1818
1919 namespace {
2020
libraries/networking/src/RequestFilters.hrenamed from interface/src/networking/RequestFilters.h+0−0

File renamed without changes.

libraries/networking/src/ResourceRequest.h+15−0
@@ -19,6 +19,21 @@ · 18 unmodified lines
1919
2020 #include "ByteRange.h"
2121
22+const QString STAT_ATP_REQUEST_STARTED = "StartedATPRequest";
23+const QString STAT_HTTP_REQUEST_STARTED = "StartedHTTPRequest";
24+const QString STAT_FILE_REQUEST_STARTED = "StartedFileRequest";
25+const QString STAT_ATP_REQUEST_SUCCESS = "SuccessfulATPRequest";
26+const QString STAT_HTTP_REQUEST_SUCCESS = "SuccessfulHTTPRequest";
27+const QString STAT_FILE_REQUEST_SUCCESS = "SuccessfulFileRequest";
28+const QString STAT_ATP_REQUEST_FAILED = "FailedATPRequest";
29+const QString STAT_HTTP_REQUEST_FAILED = "FailedHTTPRequest";
30+const QString STAT_FILE_REQUEST_FAILED = "FailedFileRequest";
31+const QString STAT_ATP_REQUEST_CACHE = "CacheATPRequest";
32+const QString STAT_HTTP_REQUEST_CACHE = "CacheHTTPRequest";
33+const QString STAT_ATP_MAPPING_REQUEST_STARTED = "StartedATPMappingRequest";
34+const QString STAT_ATP_MAPPING_REQUEST_FAILED = "FailedATPMappingRequest";
35+const QString STAT_ATP_MAPPING_REQUEST_SUCCESS = "SuccessfulATPMappingRequest";
36+
2237 class ResourceRequest : public QObject {
2338 Q_OBJECT
2439 public:
libraries/networking/src/SandboxUtils.cpp+3−4
@@ -52,9 +52,8 @@ bool readStatus(QByteArray statusData) { · 51 unmodified lines
5252 return false;
5353 }
5454
55−void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater) {
55+void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater) {
5656 QString serverPath = "./server-console/server-console.exe";
57− qCDebug(networking) << "Running marker path is: " << runningMarkerName;
5857 qCDebug(networking) << "Server path is: " << serverPath;
5958 qCDebug(networking) << "autoShutdown: " << autoShutdown;
6059 qCDebug(networking) << "noUpdater: " << noUpdater;
@@ -74,8 +73,8 @@ void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMark · 13 unmodified lines
7473 }
7574
7675 if (autoShutdown) {
77− QString interfaceRunningStateFile = RunningMarker::getMarkerFilePath(runningMarkerName);
78− args << "--shutdownWatcher" << interfaceRunningStateFile;
76+ auto pid = QCoreApplication::applicationPid();
77+ args << "--shutdownWith" << QString::number(pid);
7978 }
8079
8180 if (noUpdater) {
libraries/networking/src/SandboxUtils.h+1−1
@@ -21,7 +21,7 @@ namespace SandboxUtils { · 20 unmodified lines
2121
2222 QNetworkReply* getStatus();
2323 bool readStatus(QByteArray statusData);
24− void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater);
24+ void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater);
2525 };
2626
2727 #endif // hifi_SandboxUtils_h
libraries/networking/src/UserActivityLogger.cpp+9−1
@@ -20,7 +20,9 @@ · 19 unmodified lines
2020 #include <DependencyManager.h>
2121 #include "AddressManager.h"
2222
23−static const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
23+UserActivityLogger::UserActivityLogger() {
24+ _timer.start();
25+}
2426
2527 UserActivityLogger& UserActivityLogger::getInstance() {
2628 static UserActivityLogger sharedInstance;
@@ -44,6 +46,12 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall · 17 unmodified lines
4446 actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\"");
4547 actionPart.setBody(QByteArray().append(action));
4648 multipart->append(actionPart);
49+
50+ // Log the local-time that this event was logged
51+ QHttpPart elapsedPart;
52+ elapsedPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"elapsed_ms\"");
53+ elapsedPart.setBody(QString::number(_timer.elapsed()).toLocal8Bit());
54+ multipart->append(elapsedPart);
4755
4856 // If there are action details, add them to the multipart
4957 if (!details.isEmpty()) {
libraries/networking/src/UserActivityLogger.h+6−1
@@ -18,10 +18,13 @@ · 17 unmodified lines
1818 #include <QString>
1919 #include <QJsonObject>
2020 #include <QNetworkReply>
21+#include <QElapsedTimer>
2122
2223 #include <SettingHandle.h>
2324 #include "AddressManager.h"
2425
26+const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
27+
2528 class UserActivityLogger : public QObject {
2629 Q_OBJECT
2730
@@ -49,8 +52,10 @@ private slots: · 21 unmodified lines
4952 void requestError(QNetworkReply& errorReply);
5053
5154 private:
52− UserActivityLogger() {};
55+ UserActivityLogger();
5356 Setting::Handle<bool> _disabled { "UserActivityLoggerDisabled", false };
57+
58+ QElapsedTimer _timer;
5459 };
5560
5661 #endif // hifi_UserActivityLogger_h
libraries/networking/src/UserActivityLoggerScriptingInterface.cpp+9−1
@@ -56,7 +56,7 @@ void UserActivityLoggerScriptingInterface::palAction(QString action, QString tar · 55 unmodified lines
5656 }
5757
5858 void UserActivityLoggerScriptingInterface::palOpened(float secondsOpened) {
59− doLogAction("pal_opened", {
59+ doLogAction("pal_opened", {
6060 { "seconds_opened", secondsOpened }
6161 });
6262 }
@@ -71,6 +71,14 @@ void UserActivityLoggerScriptingInterface::makeUserConnection(QString otherID, b · 8 unmodified lines
7171 doLogAction("makeUserConnection", payload);
7272 }
7373
74+void UserActivityLoggerScriptingInterface::bubbleToggled(bool newValue) {
75+ doLogAction(newValue ? "bubbleOn" : "bubbleOff");
76+}
77+
78+void UserActivityLoggerScriptingInterface::bubbleActivated() {
79+ doLogAction("bubbleActivated");
80+}
81+
7482 void UserActivityLoggerScriptingInterface::logAction(QString action, QVariantMap details) {
7583 doLogAction(action, QJsonObject::fromVariantMap(details));
7684 }
libraries/networking/src/UserActivityLoggerScriptingInterface.h+2−0
@@ -30,6 +30,8 @@ public: · 29 unmodified lines
3030 Q_INVOKABLE void palAction(QString action, QString target);
3131 Q_INVOKABLE void palOpened(float secondsOpen);
3232 Q_INVOKABLE void makeUserConnection(QString otherUser, bool success, QString details = "");
33+ Q_INVOKABLE void bubbleToggled(bool newValue);
34+ Q_INVOKABLE void bubbleActivated();
3335 Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{});
3436 private:
3537 void doLogAction(QString action, QJsonObject details = {});
libraries/networking/src/udt/PacketHeaders.cpp+1−1
@@ -56,7 +56,7 @@ PacketVersion versionForPacketType(PacketType packetType) { · 55 unmodified lines
5656 case PacketType::AvatarData:
5757 case PacketType::BulkAvatarData:
5858 case PacketType::KillAvatar:
59− return static_cast<PacketVersion>(AvatarMixerPacketVersion::AvatarIdentitySequenceId);
59+ return static_cast<PacketVersion>(AvatarMixerPacketVersion::MannequinDefaultAvatar);
6060 case PacketType::MessagesData:
6161 return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
6262 case PacketType::ICEServerHeartbeat:
libraries/networking/src/udt/PacketHeaders.h+2−1
@@ -236,7 +236,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { · 235 unmodified lines
236236 AvatarAsChildFixes,
237237 StickAndBallDefaultAvatar,
238238 IdentityPacketsIncludeUpdateTime,
239− AvatarIdentitySequenceId
239+ AvatarIdentitySequenceId,
240+ MannequinDefaultAvatar
240241 };
241242
242243 enum class DomainConnectRequestVersion : PacketVersion {
libraries/networking/src/udt/SendQueue.cpp+3−0
@@ -241,6 +241,9 @@ void SendQueue::handshakeACK(SequenceNumber initialSequenceNumber) { · 240 unmodified lines
241241 std::lock_guard<std::mutex> locker { _handshakeMutex };
242242 _hasReceivedHandshakeACK = true;
243243 }
244+
245+ _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
246+
244247 // Notify on the handshake ACK condition
245248 _handshakeACKCondition.notify_one();
246249 }
libraries/physics/src/CharacterController.cpp+13−3
@@ -152,6 +152,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { · 151 unmodified lines
152152 btDispatcher* dispatcher = collisionWorld->getDispatcher();
153153 int numManifolds = dispatcher->getNumManifolds();
154154 bool hasFloor = false;
155+ bool isStuck = false;
155156
156157 btTransform rotation = _rigidBody->getWorldTransform();
157158 rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f)); // clear translation part
@@ -169,10 +170,18 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { · 11 unmodified lines
169170 btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame
170171 btVector3 normal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character
171172 btScalar hitHeight = _halfHeight + _radius + pointOnCharacter.dot(_currentUp);
173+ // If there's non-trivial penetration with a big impulse for several steps, we're probably stuck.
174+ // Note it here in the controller, and let MyAvatar figure out what to do about it.
175+ const float STUCK_PENETRATION = -0.05f; // always negative into the object.
176+ const float STUCK_IMPULSE = 500.0f;
177+ const int STUCK_LIFETIME = 3;
178+ if ((contact.getDistance() < STUCK_PENETRATION) && (contact.getAppliedImpulse() > STUCK_IMPULSE) && (contact.getLifeTime() > STUCK_LIFETIME)) {
179+ isStuck = true; // latch on
180+ }
172181 if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) {
173182 hasFloor = true;
174− if (!pushing) {
175− // we're not pushing against anything so we can early exit
183+ if (!pushing && isStuck) {
184+ // we're not pushing against anything and we're stuck so we can early exit
176185 // (all we need to know is that there is a floor)
177186 break;
178187 }
@@ -198,12 +207,13 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { · 19 unmodified lines
198207 _stepHeight = highestStep;
199208 _stepPoint = rotation * pointOnCharacter; // rotate into world-frame
200209 }
201− if (hasFloor && !(pushing && _stepUpEnabled)) {
210+ if (hasFloor && isStuck && !(pushing && _stepUpEnabled)) {
202211 // early exit since all we need to know is that we're on a floor
203212 break;
204213 }
205214 }
206215 }
216+ _isStuck = isStuck;
207217 return hasFloor;
208218 }
209219
libraries/physics/src/CharacterController.h+2−0
@@ -110,6 +110,7 @@ public: · 109 unmodified lines
110110 void setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale);
111111
112112 bool isEnabledAndReady() const { return _dynamicsWorld; }
113+ bool isStuck() const { return _isStuck; }
113114
114115 void setCollisionless(bool collisionless);
115116 int16_t computeCollisionGroup() const;
@@ -192,6 +193,7 @@ protected: · 76 unmodified lines
192193
193194 State _state;
194195 bool _isPushingUp;
196+ bool _isStuck { false };
195197
196198 btDynamicsWorld* _dynamicsWorld { nullptr };
197199 btRigidBody* _rigidBody { nullptr };
libraries/physics/src/EntityMotionState.cpp+22−15
@@ -65,8 +65,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer · 64 unmodified lines
6565 _lastStep(0),
6666 _loopsWithoutOwner(0),
6767 _accelerationNearlyGravityCount(0),
68− _numInactiveUpdates(1),
69− _outgoingPriority(0)
68+ _numInactiveUpdates(1)
7069 {
7170 _type = MOTIONSTATE_TYPE_ENTITY;
7271 assert(_entity);
@@ -75,6 +74,8 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer · 2 unmodified lines
7574 // we need the side-effects of EntityMotionState::setShape() so we call it explicitly here
7675 // rather than pass the legit shape pointer to the ObjectMotionState ctor above.
7776 setShape(shape);
77+
78+ _outgoingPriority = _entity->getPendingOwnershipPriority();
7879 }
7980
8081 EntityMotionState::~EntityMotionState() {
@@ -84,7 +85,7 @@ EntityMotionState::~EntityMotionState() { · 3 unmodified lines
8485
8586 void EntityMotionState::updateServerPhysicsVariables() {
8687 assert(entityTreeIsLocked());
87− if (_entity->getSimulatorID() == Physics::getSessionUUID()) {
88+ if (isLocallyOwned()) {
8889 // don't slam these values if we are the simulation owner
8990 return;
9091 }
@@ -114,6 +115,7 @@ void EntityMotionState::handleDeactivation() { · 23 unmodified lines
114115
115116 // virtual
116117 void EntityMotionState::handleEasyChanges(uint32_t& flags) {
118+ assert(_entity);
117119 assert(entityTreeIsLocked());
118120 updateServerPhysicsVariables();
119121 ObjectMotionState::handleEasyChanges(flags);
@@ -135,23 +137,23 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { · 15 unmodified lines
135137 _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
136138 }
137139 _loopsWithoutOwner = 0;
138− } else if (_entity->getSimulatorID() == Physics::getSessionUUID()) {
140+ _numInactiveUpdates = 0;
141+ } else if (isLocallyOwned()) {
139142 // we just inherited ownership, make sure our desired priority matches what we have
140143 upgradeOutgoingPriority(_entity->getSimulationPriority());
141144 } else {
142145 _outgoingPriority = 0;
143146 _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
147+ _numInactiveUpdates = 0;
144148 }
145149 }
146150 if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) {
147− // The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bits really mean "we should bid for ownership because
148− // a local script has been changing physics properties, or we should adjust our own ownership priority".
149− // The desired priority is determined by which bits were set.
150− if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB) {
151− _outgoingPriority = SCRIPT_GRAB_SIMULATION_PRIORITY;
152− } else {
153− _outgoingPriority = SCRIPT_POKE_SIMULATION_PRIORITY;
154− }
151+ // The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bit means one of the following:
152+ // (1) we own it but may need to change the priority OR...
153+ // (2) we don't own it but should bid (because a local script has been changing physics properties)
154+ uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority();
155+ _outgoingPriority = glm::max(_outgoingPriority, newPriority);
156+
155157 // reset bid expiry so that we bid ASAP
156158 _nextOwnershipBid = 0;
157159 }
@@ -170,6 +172,7 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { · 12 unmodified lines
170172
171173 // virtual
172174 bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
175+ assert(_entity);
173176 updateServerPhysicsVariables();
174177 return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
175178 }
@@ -315,7 +318,7 @@ bool EntityMotionState::isCandidateForOwnership() const { · 139 unmodified lines
315318 assert(_entity);
316319 assert(entityTreeIsLocked());
317320 return _outgoingPriority != 0
318− || Physics::getSessionUUID() == _entity->getSimulatorID()
321+ || isLocallyOwned()
319322 || _entity->dynamicDataNeedsTransmit();
320323 }
321324
@@ -489,7 +492,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { · 167 unmodified lines
489492 return true;
490493 }
491494
492− if (_entity->getSimulatorID() != Physics::getSessionUUID()) {
495+ if (!isLocallyOwned()) {
493496 // we don't own the simulation
494497
495498 // NOTE: we do not volunteer to own kinematic or static objects
@@ -597,7 +600,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ · 101 unmodified lines
597600 properties.clearSimulationOwner();
598601 _outgoingPriority = 0;
599602 _entity->setPendingOwnershipPriority(_outgoingPriority, now);
600− } else if (Physics::getSessionUUID() != _entity->getSimulatorID()) {
603+ } else if (!isLocallyOwned()) {
601604 // we don't own the simulation for this entity yet, but we're sending a bid for it
602605 quint8 bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY);
603606 properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority);
@@ -786,6 +789,10 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma · 182 unmodified lines
786789 _entity->computeCollisionGroupAndFinalMask(group, mask);
787790 }
788791
792+bool EntityMotionState::isLocallyOwned() const {
793+ return _entity->getSimulatorID() == Physics::getSessionUUID();
794+}
795+
789796 bool EntityMotionState::shouldBeLocallyOwned() const {
790797 return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) ||
791798 _entity->getSimulatorID() == Physics::getSessionUUID();
libraries/physics/src/EntityMotionState.h+1−0
@@ -79,6 +79,7 @@ public: · 78 unmodified lines
7979
8080 virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
8181
82+ bool isLocallyOwned() const override;
8283 bool shouldBeLocallyOwned() const override;
8384
8485 friend class PhysicalEntitySimulation;
libraries/physics/src/ObjectMotionState.cpp+2−0
@@ -202,6 +202,7 @@ void ObjectMotionState::setShape(const btCollisionShape* shape) { · 201 unmodified lines
202202 }
203203
204204 void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
205+ assert(_body && _shape);
205206 if (flags & Simulation::DIRTY_POSITION) {
206207 btTransform worldTrans = _body->getWorldTransform();
207208 btVector3 newPosition = glmToBullet(getObjectPosition());
@@ -282,6 +283,7 @@ void ObjectMotionState::handleEasyChanges(uint32_t& flags) { · 74 unmodified lines
282283 }
283284
284285 bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
286+ assert(_body && _shape);
285287 if (flags & Simulation::DIRTY_SHAPE) {
286288 // make sure the new shape is valid
287289 if (!isReadyToComputeShape()) {
libraries/physics/src/ObjectMotionState.h+2−1
@@ -79,7 +79,7 @@ public: · 78 unmodified lines
7979 static ShapeManager* getShapeManager();
8080
8181 ObjectMotionState(const btCollisionShape* shape);
82− ~ObjectMotionState();
82+ virtual ~ObjectMotionState();
8383
8484 virtual void handleEasyChanges(uint32_t& flags);
8585 virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine);
@@ -146,6 +146,7 @@ public: · 60 unmodified lines
146146 void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; }
147147 void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; }
148148
149+ virtual bool isLocallyOwned() const { return false; }
149150 virtual bool shouldBeLocallyOwned() const { return false; }
150151
151152 friend class PhysicsEngine;
libraries/physics/src/PhysicalEntitySimulation.cpp+1−1
@@ -130,7 +130,7 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { · 129 unmodified lines
130130 }
131131
132132 // then remove the objects (aka MotionStates) from physics
133− _physicsEngine->removeObjects(_physicalObjects);
133+ _physicsEngine->removeSetOfObjects(_physicalObjects);
134134
135135 // delete the MotionStates
136136 // TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete
libraries/physics/src/PhysicsEngine.cpp+25−20
@@ -129,6 +129,9 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) { · 128 unmodified lines
129129 }
130130 body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
131131 body->updateInertiaTensor();
132+ if (motionState->isLocallyOwned()) {
133+ _activeStaticBodies.insert(body);
134+ }
132135 break;
133136 }
134137 }
@@ -174,19 +177,9 @@ void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) { · 39 unmodified lines
174177 // frame (because the framerate is faster than our physics simulation rate). When this happens we must scan
175178 // _activeStaticBodies for objects that were recently deleted so we don't try to access a dangling pointer.
176179 for (auto object : objects) {
177− btRigidBody* body = object->getRigidBody();
178−
179− std::vector<btRigidBody*>::reverse_iterator itr = _activeStaticBodies.rbegin();
180− while (itr != _activeStaticBodies.rend()) {
181− if (body == *itr) {
182− if (*itr != *(_activeStaticBodies.rbegin())) {
183− // swap with rbegin
184− *itr = *(_activeStaticBodies.rbegin());
185− }
186− _activeStaticBodies.pop_back();
187− break;
188− }
189− ++itr;
180+ std::set<btRigidBody*>::iterator itr = _activeStaticBodies.find(object->getRigidBody());
181+ if (itr != _activeStaticBodies.end()) {
182+ _activeStaticBodies.erase(itr);
190183 }
191184 }
192185 }
@@ -207,7 +200,7 @@ void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) { · 14 unmodified lines
207200 }
208201
209202 // Same as above, but takes a Set instead of a Vector. Should only be called during teardown.
210−void PhysicsEngine::removeObjects(const SetOfMotionStates& objects) {
203+void PhysicsEngine::removeSetOfObjects(const SetOfMotionStates& objects) {
211204 _contactMap.clear();
212205 for (auto object : objects) {
213206 btRigidBody* body = object->getRigidBody();
@@ -245,14 +238,16 @@ VectorOfMotionStates PhysicsEngine::changeObjects(const VectorOfMotionStates& ob · 31 unmodified lines
245238 object->clearIncomingDirtyFlags();
246239 }
247240 if (object->getMotionType() == MOTION_TYPE_STATIC && object->isActive()) {
248− _activeStaticBodies.push_back(object->getRigidBody());
241+ _activeStaticBodies.insert(object->getRigidBody());
249242 }
250243 }
251244 // active static bodies have changed (in an Easy way) and need their Aabbs updated
252245 // but we've configured Bullet to NOT update them automatically (for improved performance)
253246 // so we must do it ourselves
254− for (size_t i = 0; i < _activeStaticBodies.size(); ++i) {
255− _dynamicsWorld->updateSingleAabb(_activeStaticBodies[i]);
247+ std::set<btRigidBody*>::const_iterator itr = _activeStaticBodies.begin();
248+ while (itr != _activeStaticBodies.end()) {
249+ _dynamicsWorld->updateSingleAabb(*itr);
250+ ++itr;
256251 }
257252 return stillNeedChange;
258253 }
@@ -496,13 +491,23 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { · 237 unmodified lines
496491
497492 const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() {
498493 BT_PROFILE("copyOutgoingChanges");
494+
495+ _dynamicsWorld->synchronizeMotionStates();
496+
499497 // Bullet will not deactivate static objects (it doesn't expect them to be active)
500498 // so we must deactivate them ourselves
501− for (size_t i = 0; i < _activeStaticBodies.size(); ++i) {
502− _activeStaticBodies[i]->forceActivationState(ISLAND_SLEEPING);
499+ std::set<btRigidBody*>::const_iterator itr = _activeStaticBodies.begin();
500+ while (itr != _activeStaticBodies.end()) {
501+ btRigidBody* body = *itr;
502+ body->forceActivationState(ISLAND_SLEEPING);
503+ ObjectMotionState* motionState = static_cast<ObjectMotionState*>(body->getUserPointer());
504+ if (motionState) {
505+ _dynamicsWorld->addChangedMotionState(motionState);
506+ }
507+ ++itr;
503508 }
504509 _activeStaticBodies.clear();
505− _dynamicsWorld->synchronizeMotionStates();
510+
506511 _hasOutgoingChanges = false;
507512 return _dynamicsWorld->getChangedMotionStates();
508513 }
libraries/physics/src/PhysicsEngine.h+3−2
@@ -13,6 +13,7 @@ · 12 unmodified lines
1313 #define hifi_PhysicsEngine_h
1414
1515 #include <stdint.h>
16+#include <set>
1617 #include <vector>
1718
1819 #include <QUuid>
@@ -53,7 +54,7 @@ public: · 34 unmodified lines
5354 uint32_t getNumSubsteps();
5455
5556 void removeObjects(const VectorOfMotionStates& objects);
56− void removeObjects(const SetOfMotionStates& objects); // only called during teardown
57+ void removeSetOfObjects(const SetOfMotionStates& objects); // only called during teardown
5758
5859 void addObjects(const VectorOfMotionStates& objects);
5960 VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects);
@@ -114,7 +115,7 @@ private: · 54 unmodified lines
114115 CollisionEvents _collisionEvents;
115116 QHash<QUuid, EntityDynamicPointer> _objectDynamics;
116117 QHash<btRigidBody*, QSet<QUuid>> _objectDynamicsByBody;
117− std::vector<btRigidBody*> _activeStaticBodies;
118+ std::set<btRigidBody*> _activeStaticBodies;
118119
119120 glm::vec3 _originOffset;
120121
libraries/physics/src/ThreadSafeDynamicsWorld.h+2−0
@@ -51,6 +51,8 @@ public: · 50 unmodified lines
5151 const VectorOfMotionStates& getChangedMotionStates() const { return _changedMotionStates; }
5252 const VectorOfMotionStates& getDeactivatedMotionStates() const { return _deactivatedStates; }
5353
54+ void addChangedMotionState(ObjectMotionState* motionState) { _changedMotionStates.push_back(motionState); }
55+
5456 private:
5557 // call this instead of non-virtual btDiscreteDynamicsWorld::synchronizeSingleMotionState()
5658 void synchronizeMotionState(btRigidBody* body);
libraries/plugins/src/plugins/DisplayPlugin.h+1−0
@@ -200,6 +200,7 @@ public: · 199 unmodified lines
200200 virtual float newFramePresentRate() const { return -1.0f; }
201201 // Rate at which rendered frames are being skipped
202202 virtual float droppedFrameRate() const { return -1.0f; }
203+ virtual bool getSupportsAutoSwitch() { return false; }
203204
204205 // Hardware specific stats
205206 virtual QJsonObject getHardwareStats() const { return QJsonObject(); }
libraries/plugins/src/plugins/InputPlugin.h+2−1
@@ -24,6 +24,7 @@ public: · 23 unmodified lines
2424 // Some input plugins are comprised of multiple subdevices (SDL2, for instance).
2525 // If an input plugin is only a single device, it will only return it's primary name.
2626 virtual QStringList getSubdeviceNames() { return { getName() }; };
27− virtual bool isHandController() const = 0;
27+ virtual bool isHandController() const { return false; }
28+ virtual bool isHeadController() const { return false; }
2829 };
2930
libraries/plugins/src/plugins/Plugin.h+13−0
@@ -53,6 +53,18 @@ public: · 52 unmodified lines
5353 virtual bool isActive() {
5454 return _active;
5555 }
56+ virtual bool startStandBySession() {
57+ _sessionStatus = true;
58+ return _sessionStatus;
59+ }
60+
61+ virtual void endSession() {
62+ _sessionStatus = false;
63+ }
64+
65+ virtual bool isSessionActive() {
66+ return _sessionStatus;
67+ }
5668
5769 /**
5870 * Called by the application during it's idle phase. If the plugin needs to do
@@ -73,6 +85,7 @@ signals: · 14 unmodified lines
7385
7486 protected:
7587 bool _active { false };
88+ bool _sessionStatus { false };
7689 PluginContainer* _container { nullptr };
7790 static const char* UNKNOWN_PLUGIN_ID;
7891
libraries/plugins/src/plugins/PluginUtils.cpp+9−0
@@ -24,6 +24,15 @@ bool PluginUtils::isHMDAvailable(const QString& pluginName) { · 23 unmodified lines
2424 return false;
2525 }
2626
27+bool PluginUtils::isHeadControllerAvailable(const QString& pluginName) {
28+ for (auto& inputPlugin : PluginManager::getInstance()->getInputPlugins()) {
29+ if (inputPlugin->isHeadController() && (pluginName.isEmpty() || inputPlugin->getName() == pluginName)) {
30+ return true;
31+ }
32+ }
33+ return false;
34+};
35+
2736 bool PluginUtils::isHandControllerAvailable(const QString& pluginName) {
2837 for (auto& inputPlugin : PluginManager::getInstance()->getInputPlugins()) {
2938 if (inputPlugin->isHandController() && (pluginName.isEmpty() || inputPlugin->getName() == pluginName)) {
libraries/plugins/src/plugins/PluginUtils.h+1−0
@@ -16,6 +16,7 @@ class PluginUtils { · 15 unmodified lines
1616 public:
1717 static bool isHMDAvailable(const QString& pluginName = "");
1818 static bool isHandControllerAvailable(const QString& pluginName = "");
19+ static bool isHeadControllerAvailable(const QString& pluginName = "");
1920 static bool isSubdeviceContainingNameAvailable(QString name);
2021 static bool isViveControllerAvailable();
2122 static bool isOculusTouchControllerAvailable();
libraries/procedural/src/procedural/ProceduralSkybox.cpp+1−1
@@ -23,7 +23,7 @@ ProceduralSkybox::ProceduralSkybox() : model::Skybox() { · 22 unmodified lines
2323 _procedural._fragmentSource = skybox_frag;
2424 // Adjust the pipeline state for background using the stencil test
2525 _procedural.setDoesFade(false);
26− _procedural._opaqueState->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
26+ _procedural._opaqueState->setStencilTest(true, 0xFF, gpu::State::StencilTest(1, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
2727 }
2828
2929 bool ProceduralSkybox::empty() {
libraries/render-utils/CMakeLists.txt+1−1
@@ -3,7 +3,7 @@ AUTOSCRIBE_SHADER_LIB(gpu model render) · 2 unmodified lines
33 # pull in the resources.qrc file
44 qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc")
55 setup_hifi_library(Widgets OpenGL Network Qml Quick Script)
6−link_hifi_libraries(shared ktx gpu model model-networking render animation fbx entities image)
6+link_hifi_libraries(shared ktx gpu model model-networking render animation fbx entities image procedural)
77
88 if (NOT ANDROID)
99 target_nsight()
libraries/render-utils/src/AnimDebugDraw.cpp+1−1
@@ -260,7 +260,7 @@ static void addLink(const AnimPose& rootPose, const AnimPose& pose, const AnimPo · 259 unmodified lines
260260 // there is room, so lets draw a nice bone
261261
262262 glm::vec3 uAxis, vAxis, wAxis;
263− generateBasisVectors(boneAxis0, glm::vec3(1, 0, 0), uAxis, vAxis, wAxis);
263+ generateBasisVectors(boneAxis0, glm::vec3(1.0f, 0.0f, 0.0f), uAxis, vAxis, wAxis);
264264
265265 glm::vec3 boneBaseCorners[NUM_BASE_CORNERS];
266266 boneBaseCorners[0] = pose0 * ((uAxis * radius) + (vAxis * radius) + (wAxis * radius));
libraries/render-utils/src/AntialiasingEffect.cpp+4−4
@@ -17,6 +17,7 @@ · 16 unmodified lines
1717 #include <gpu/Context.h>
1818
1919 #include "AntialiasingEffect.h"
20+#include "StencilMaskPass.h"
2021 #include "TextureCache.h"
2122 #include "FramebufferCache.h"
2223 #include "DependencyManager.h"
@@ -70,6 +71,8 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { · 47 unmodified lines
7071
7172 gpu::StatePointer state = gpu::StatePointer(new gpu::State());
7273
74+ PrepareStencil::testMask(*state);
75+
7376 state->setDepthTest(false, false, gpu::LESS_EQUAL);
7477
7578 // Good to go add the brand new pipeline
@@ -93,6 +96,7 @@ const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { · 17 unmodified lines
9396 gpu::StatePointer state = gpu::StatePointer(new gpu::State());
9497
9598 state->setDepthTest(false, false, gpu::LESS_EQUAL);
99+ PrepareStencil::testMask(*state);
96100
97101 // Good to go add the brand new pipeline
98102 _blendPipeline = gpu::Pipeline::create(program, state);
@@ -106,10 +110,6 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const · 7 unmodified lines
106110
107111 RenderArgs* args = renderContext->args;
108112
109− if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) {
110− return;
111− }
112−
113113 gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
114114 batch.enableStereo(false);
115115 batch.setViewportTransform(args->_viewport);
libraries/render-utils/src/BackgroundStage.cpp+140−0
@@ -0,0 +1,140 @@
1+//
2+// BackgroundStage.cpp
3+//
4+// Created by Sam Gateau on 5/9/2017.
5+// Copyright 2015 High Fidelity, Inc.
6+//
7+// Distributed under the Apache License, Version 2.0.
8+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
9+//
10+#include "BackgroundStage.h"
11+
12+#include "DeferredLightingEffect.h"
13+
14+#include <gpu/Context.h>
15+
16+BackgroundStage::Index BackgroundStage::findBackground(const BackgroundPointer& background) const {
17+ auto found = _backgroundMap.find(background);
18+ if (found != _backgroundMap.end()) {
19+ return INVALID_INDEX;
20+ } else {
21+ return (*found).second;
22+ }
23+
24+}
25+
26+BackgroundStage::Index BackgroundStage::addBackground(const BackgroundPointer& background) {
27+
28+ auto found = _backgroundMap.find(background);
29+ if (found == _backgroundMap.end()) {
30+ auto backgroundId = _backgrounds.newElement(background);
31+ // Avoid failing to allocate a background, just pass
32+ if (backgroundId != INVALID_INDEX) {
33+
34+ // Insert the background and its index in the reverse map
35+ _backgroundMap.insert(BackgroundMap::value_type(background, backgroundId));
36+ }
37+ return backgroundId;
38+ } else {
39+ return (*found).second;
40+ }
41+}
42+
43+
44+BackgroundStage::BackgroundPointer BackgroundStage::removeBackground(Index index) {
45+ BackgroundPointer removed = _backgrounds.freeElement(index);
46+
47+ if (removed) {
48+ _backgroundMap.erase(removed);
49+ }
50+ return removed;
51+}
52+
53+
54+void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) {
55+
56+ const auto& lightingModel = inputs;
57+ if (!lightingModel->isBackgroundEnabled()) {
58+ return;
59+ }
60+
61+
62+ // Background rendering decision
63+ auto backgroundStage = DependencyManager::get<DeferredLightingEffect>()->getBackgroundStage();
64+ model::SunSkyStagePointer background;
65+ model::SkyboxPointer skybox;
66+ if (backgroundStage->_currentFrame._backgrounds.size()) {
67+ auto backgroundId = backgroundStage->_currentFrame._backgrounds.front();
68+ auto background = backgroundStage->getBackground(backgroundId);
69+ if (background) {
70+ skybox = background->getSkybox();
71+ }
72+ } else {
73+ skybox = DependencyManager::get<DeferredLightingEffect>()->getDefaultSkybox();
74+ }
75+
76+ /* auto backgroundMode = skyStage->getBackgroundMode();
77+
78+ switch (backgroundMode) {
79+ case model::SunSkyStage::SKY_DEFAULT: {
80+ auto scene = DependencyManager::get<SceneScriptingInterface>()->getStage();
81+ auto sceneKeyLight = scene->getKeyLight();
82+
83+ scene->setSunModelEnable(false);
84+ sceneKeyLight->setColor(ColorUtils::toVec3(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_COLOR));
85+ sceneKeyLight->setIntensity(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_INTENSITY);
86+ sceneKeyLight->setAmbientIntensity(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY);
87+ sceneKeyLight->setDirection(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_DIRECTION);
88+ // fall through: render a skybox (if available), or the defaults (if requested)
89+ }
90+
91+ case model::SunSkyStage::SKY_BOX: {*/
92+ if (skybox && !skybox->empty()) {
93+ PerformanceTimer perfTimer("skybox");
94+ auto args = renderContext->args;
95+
96+ gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
97+ args->_batch = &batch;
98+
99+ batch.enableSkybox(true);
100+
101+ batch.setViewportTransform(args->_viewport);
102+ batch.setStateScissorRect(args->_viewport);
103+
104+ glm::mat4 projMat;
105+ Transform viewMat;
106+ args->getViewFrustum().evalProjectionMatrix(projMat);
107+ args->getViewFrustum().evalViewTransform(viewMat);
108+
109+ batch.setProjectionTransform(projMat);
110+ batch.setViewTransform(viewMat);
111+
112+ skybox->render(batch, args->getViewFrustum());
113+ });
114+ args->_batch = nullptr;
115+
116+ // break;
117+ }
118+ // fall through: render defaults (if requested)
119+// }
120+/*
121+ case model::SunSkyStage::SKY_DEFAULT_AMBIENT_TEXTURE: {
122+ if (Menu::getInstance()->isOptionChecked(MenuOption::DefaultSkybox)) {
123+ auto scene = DependencyManager::get<SceneScriptingInterface>()->getStage();
124+ auto sceneKeyLight = scene->getKeyLight();
125+ auto defaultSkyboxAmbientTexture = qApp->getDefaultSkyboxAmbientTexture();
126+ if (defaultSkyboxAmbientTexture) {
127+ sceneKeyLight->setAmbientSphere(defaultSkyboxAmbientTexture->getIrradiance());
128+ sceneKeyLight->setAmbientMap(defaultSkyboxAmbientTexture);
129+ } else {
130+ static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(
131+ "Failed to get a valid Default Skybox Ambient Texture ? probably because it couldn't be find during initialization step");
132+ }
133+ // fall through: render defaults skybox
134+ } else {
135+ break;
136+ }
137+ }
138+ */
139+
140+}
libraries/render-utils/src/BackgroundStage.h+80−0
@@ -0,0 +1,80 @@
1+//
2+// BackgroundStage.h
3+
4+// Created by Sam Gateau on 5/9/2017.
5+// Copyright 2015 High Fidelity, Inc.
6+//
7+// Distributed under the Apache License, Version 2.0.
8+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
9+//
10+
11+#ifndef hifi_render_utils_BackgroundStage_h
12+#define hifi_render_utils_BackgroundStage_h
13+
14+#include <model/Stage.h>
15+#include <set>
16+#include <unordered_map>
17+#include <render/IndexedContainer.h>
18+
19+#include "LightingModel.h"
20+
21+
22+// Background stage to set up background-related rendering tasks
23+class BackgroundStage {
24+public:
25+ using Index = render::indexed_container::Index;
26+ static const Index INVALID_INDEX { render::indexed_container::INVALID_INDEX };
27+ static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; }
28+
29+ using BackgroundPointer = model::SunSkyStagePointer;
30+ using Backgrounds = render::indexed_container::IndexedPointerVector<model::SunSkyStage>;
31+ using BackgroundMap = std::unordered_map<BackgroundPointer, Index>;
32+
33+ using BackgroundIndices = std::vector<Index>;
34+
35+
36+ Index findBackground(const BackgroundPointer& background) const;
37+ Index addBackground(const BackgroundPointer& background);
38+
39+ BackgroundPointer removeBackground(Index index);
40+
41+ bool checkBackgroundId(Index index) const { return _backgrounds.checkIndex(index); }
42+
43+ Index getNumBackgrounds() const { return _backgrounds.getNumElements(); }
44+ Index getNumFreeBackgrounds() const { return _backgrounds.getNumFreeIndices(); }
45+ Index getNumAllocatedBackgrounds() const { return _backgrounds.getNumAllocatedIndices(); }
46+
47+ BackgroundPointer getBackground(Index backgroundId) const {
48+ return _backgrounds.get(backgroundId);
49+ }
50+
51+ Backgrounds _backgrounds;
52+ BackgroundMap _backgroundMap;
53+
54+ class Frame {
55+ public:
56+ Frame() {}
57+
58+ void clear() { _backgrounds.clear(); }
59+
60+ void pushBackground(BackgroundStage::Index index) { _backgrounds.emplace_back(index); }
61+
62+ BackgroundStage::BackgroundIndices _backgrounds;
63+ };
64+
65+ Frame _currentFrame;
66+};
67+using BackgroundStagePointer = std::shared_ptr<BackgroundStage>;
68+
69+
70+class DrawBackgroundStage {
71+public:
72+ using Inputs = LightingModelPointer;
73+ using JobModel = render::Job::ModelI<DrawBackgroundStage, Inputs>;
74+
75+ void run(const render::RenderContextPointer& renderContext, const Inputs& inputs);
76+
77+protected:
78+};
79+
80+#endif
libraries/render-utils/src/DeferredLightingEffect.cpp+51−126
@@ -18,6 +18,7 @@ · 17 unmodified lines
1818 #include <gpu/Batch.h>
1919 #include <gpu/Context.h>
2020
21+#include "StencilMaskPass.h"
2122 #include "AbstractViewStateInterface.h"
2223 #include "GeometryCache.h"
2324 #include "TextureCache.h"
@@ -27,18 +28,15 @@ · 3 unmodified lines
2728 #include "deferred_light_point_vert.h"
2829 #include "deferred_light_spot_vert.h"
2930
30−#include "directional_light_frag.h"
3131 #include "directional_ambient_light_frag.h"
3232 #include "directional_skybox_light_frag.h"
3333
34−#include "directional_light_shadow_frag.h"
3534 #include "directional_ambient_light_shadow_frag.h"
3635 #include "directional_skybox_light_shadow_frag.h"
3736
3837 #include "local_lights_shading_frag.h"
3938 #include "local_lights_drawOutline_frag.h"
40−#include "point_light_frag.h"
41−#include "spot_light_frag.h"
39+
4240
4341 using namespace render;
4442
@@ -82,48 +80,26 @@ enum DeferredShader_BufferSlot { · 37 unmodified lines
8280 };
8381
8482 static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations);
85−static void loadLightVolumeProgram(const char* vertSource, const char* fragSource, bool front, gpu::PipelinePointer& program, LightLocationsPtr& locations);
86−
87−const char no_light_frag[] =
88−R"SCRIBE(
89−out vec4 _fragColor;
90−
91−void main(void) {
92− _fragColor = vec4(1.0, 1.0, 1.0, 1.0);
93−}
94−)SCRIBE"
95−;
9683
9784 void DeferredLightingEffect::init() {
98− _directionalLightLocations = std::make_shared<LightLocations>();
9985 _directionalAmbientSphereLightLocations = std::make_shared<LightLocations>();
10086 _directionalSkyboxLightLocations = std::make_shared<LightLocations>();
10187
102− _directionalLightShadowLocations = std::make_shared<LightLocations>();
10388 _directionalAmbientSphereLightShadowLocations = std::make_shared<LightLocations>();
10489 _directionalSkyboxLightShadowLocations = std::make_shared<LightLocations>();
10590
10691 _localLightLocations = std::make_shared<LightLocations>();
10792 _localLightOutlineLocations = std::make_shared<LightLocations>();
108− _pointLightLocations = std::make_shared<LightLocations>();
109− _spotLightLocations = std::make_shared<LightLocations>();
11093
111− loadLightProgram(deferred_light_vert, directional_light_frag, false, _directionalLight, _directionalLightLocations);
11294 loadLightProgram(deferred_light_vert, directional_ambient_light_frag, false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations);
11395 loadLightProgram(deferred_light_vert, directional_skybox_light_frag, false, _directionalSkyboxLight, _directionalSkyboxLightLocations);
11496
115− loadLightProgram(deferred_light_vert, directional_light_shadow_frag, false, _directionalLightShadow, _directionalLightShadowLocations);
11697 loadLightProgram(deferred_light_vert, directional_ambient_light_shadow_frag, false, _directionalAmbientSphereLightShadow, _directionalAmbientSphereLightShadowLocations);
11798 loadLightProgram(deferred_light_vert, directional_skybox_light_shadow_frag, false, _directionalSkyboxLightShadow, _directionalSkyboxLightShadowLocations);
11899
119100 loadLightProgram(deferred_light_vert, local_lights_shading_frag, true, _localLight, _localLightLocations);
120101 loadLightProgram(deferred_light_vert, local_lights_drawOutline_frag, true, _localLightOutline, _localLightOutlineLocations);
121102
122− loadLightVolumeProgram(deferred_light_point_vert, no_light_frag, false, _pointLightBack, _pointLightLocations);
123− loadLightVolumeProgram(deferred_light_point_vert, no_light_frag, true, _pointLightFront, _pointLightLocations);
124− loadLightVolumeProgram(deferred_light_spot_vert, no_light_frag, false, _spotLightBack, _spotLightLocations);
125− loadLightVolumeProgram(deferred_light_spot_vert, no_light_frag, true, _spotLightFront, _spotLightLocations);
126−
127103 // Light Stage and clusters
128104 _lightStage = std::make_shared<LightStage>();
129105
@@ -141,79 +117,69 @@ void DeferredLightingEffect::init() { · 11 unmodified lines
141117 _globalLights.push_back(_lightStage->addLight(lp));
142118 _lightStage->addShadow(_globalLights[0]);
143119
144−}
145120
146−void DeferredLightingEffect::addLight(const model::LightPointer& light) {
147− assert(light);
148− auto lightID = _lightStage->addLight(light);
149− if (light->getType() == model::Light::POINT) {
150− _pointLights.push_back(lightID);
151− } else {
152− _spotLights.push_back(lightID);
153− }
154−}
121+ _backgroundStage = std::make_shared<BackgroundStage>();
155122
123+ auto textureCache = DependencyManager::get<TextureCache>();
156124
157−void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radius, const glm::vec3& color,
158− float intensity, float falloffRadius) {
159− addSpotLight(position, radius, color, intensity, falloffRadius);
160−}
125+ {
126+ PROFILE_RANGE(render, "Process Default Skybox");
127+ auto textureCache = DependencyManager::get<TextureCache>();
161128
162−void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color,
163− float intensity, float falloffRadius, const glm::quat& orientation, float exponent, float cutoff) {
164−
165− unsigned int lightID = (unsigned int)(_pointLights.size() + _spotLights.size() + _globalLights.size());
166− if (lightID >= _allocatedLights.size()) {
167− _allocatedLights.push_back(std::make_shared<model::Light>());
129+ auto skyboxUrl = PathUtils::resourcesPath().toStdString() + "images/Default-Sky-9-cubemap.ktx";
130+
131+ _defaultSkyboxTexture = gpu::Texture::unserialize(skyboxUrl);
132+ _defaultSkyboxAmbientTexture = _defaultSkyboxTexture;
133+
134+ _defaultSkybox->setCubemap(_defaultSkyboxTexture);
168135 }
169− model::LightPointer lp = _allocatedLights[lightID];
170136
171− lp->setPosition(position);
172− lp->setMaximumRadius(radius);
173− lp->setColor(color);
174− lp->setIntensity(intensity);
175− lp->setFalloffRadius(falloffRadius);
176137
177− if (exponent == 0.0f && cutoff == PI) {
178− lp->setType(model::Light::POINT);
179− _pointLights.push_back(lightID);
180−
181− } else {
182− lp->setOrientation(orientation);
183− lp->setSpotAngle(cutoff);
184− lp->setSpotExponent(exponent);
185− lp->setType(model::Light::SPOT);
186− _spotLights.push_back(lightID);
138+ lp->setAmbientIntensity(0.5f);
139+ lp->setAmbientMap(_defaultSkyboxAmbientTexture);
140+ auto irradianceSH = _defaultSkyboxAmbientTexture->getIrradiance();
141+ if (irradianceSH) {
142+ lp->setAmbientSphere((*irradianceSH));
187143 }
188144 }
189145
190146 void DeferredLightingEffect::setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) {
191147 PerformanceTimer perfTimer("DLE->setupBatch()");
192− auto keyLight = _allocatedLights[_globalLights.front()];
148+ model::LightPointer keySunLight;
149+ if (_lightStage && _lightStage->_currentFrame._sunLights.size()) {
150+ keySunLight = _lightStage->getLight(_lightStage->_currentFrame._sunLights.front());
151+ } else {
152+ keySunLight = _allocatedLights[_globalLights.front()];
153+ }
154+
155+ model::LightPointer keyAmbiLight;
156+ if (_lightStage && _lightStage->_currentFrame._ambientLights.size()) {
157+ keyAmbiLight = _lightStage->getLight(_lightStage->_currentFrame._ambientLights.front());
158+ } else {
159+ keyAmbiLight = _allocatedLights[_globalLights.front()];
160+ }
193161
194162 if (lightBufferUnit >= 0) {
195− batch.setUniformBuffer(lightBufferUnit, keyLight->getLightSchemaBuffer());
163+ batch.setUniformBuffer(lightBufferUnit, keySunLight->getLightSchemaBuffer());
196164 }
197− if (keyLight->hasAmbient() && (ambientBufferUnit >= 0)) {
198− batch.setUniformBuffer(ambientBufferUnit, keyLight->getAmbientSchemaBuffer());
165+ if (ambientBufferUnit >= 0) {
166+ batch.setUniformBuffer(ambientBufferUnit, keyAmbiLight->getAmbientSchemaBuffer());
199167 }
200168
201− if (keyLight->getAmbientMap() && (skyboxCubemapUnit >= 0)) {
202− batch.setResourceTexture(skyboxCubemapUnit, keyLight->getAmbientMap());
169+ if (keyAmbiLight->getAmbientMap() && (skyboxCubemapUnit >= 0)) {
170+ batch.setResourceTexture(skyboxCubemapUnit, keyAmbiLight->getAmbientMap());
203171 }
204172 }
205173
206174 void DeferredLightingEffect::unsetKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) {
207− auto keyLight = _allocatedLights[_globalLights.front()];
208−
209175 if (lightBufferUnit >= 0) {
210176 batch.setUniformBuffer(lightBufferUnit, nullptr);
211177 }
212− if (keyLight->hasAmbient() && (ambientBufferUnit >= 0)) {
178+ if ((ambientBufferUnit >= 0)) {
213179 batch.setUniformBuffer(ambientBufferUnit, nullptr);
214180 }
215181
216− if (keyLight->getAmbientMap() && (skyboxCubemapUnit >= 0)) {
182+ if ((skyboxCubemapUnit >= 0)) {
217183 batch.setResourceTexture(skyboxCubemapUnit, nullptr);
218184 }
219185 }
@@ -277,7 +243,7 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo · 57 unmodified lines
277243 state->setColorWriteMask(true, true, true, false);
278244
279245 if (lightVolume) {
280− state->setStencilTest(true, 0x00, gpu::State::StencilTest(1, 0xFF, gpu::LESS_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
246+ PrepareStencil::testShape(*state);
281247
282248 state->setCullMode(gpu::State::CULL_BACK);
283249 // state->setCullMode(gpu::State::CULL_FRONT);
@@ -290,7 +256,7 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo · 6 unmodified lines
290256
291257 } else {
292258 // Stencil test all the light passes for objects pixels only, not the background
293− state->setStencilTest(true, 0x00, gpu::State::StencilTest(0, 0x01, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
259+ PrepareStencil::testShape(*state);
294260
295261 state->setCullMode(gpu::State::CULL_BACK);
296262 // additive blending
@@ -300,49 +266,21 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo · 3 unmodified lines
300266
301267 }
302268
303−
304−static void loadLightVolumeProgram(const char* vertSource, const char* fragSource, bool front, gpu::PipelinePointer& pipeline, LightLocationsPtr& locations) {
305− gpu::ShaderPointer program = makeLightProgram(vertSource, fragSource, locations);
306−
307− auto state = std::make_shared<gpu::State>();
308−
309− // Stencil test all the light passes for objects pixels only, not the background
310−
311− if (front) {
312− state->setCullMode(gpu::State::CULL_BACK);
313− state->setDepthTest(true, false, gpu::LESS_EQUAL);
314− state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_DECR, gpu::State::STENCIL_OP_KEEP));
315−
316− // state->setDepthClampEnable(true);
317− // TODO: We should use DepthClamp and avoid changing geometry for inside /outside cases
318− // additive blending
319− // state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
320−
321− //state->setColorWriteMask(true, true, true, false);
322− state->setColorWriteMask(false, false, false, false);
323− } else {
324− state->setCullMode(gpu::State::CULL_FRONT);
325− state->setDepthTest(true, false, gpu::LESS_EQUAL);
326− state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_INCR, gpu::State::STENCIL_OP_KEEP));
327− // additive blending
328− // state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
329− // state->setColorWriteMask(true, true, true, false);
330− state->setColorWriteMask(false, false, false, false);
331− }
332− pipeline = gpu::Pipeline::create(program, state);
333−
334−}
335−
336269 void DeferredLightingEffect::setGlobalLight(const model::LightPointer& light) {
337− auto globalLight = _allocatedLights.front();
270+ /* auto globalLight = _allocatedLights.front();
338271 globalLight->setDirection(light->getDirection());
339272 globalLight->setColor(light->getColor());
340273 globalLight->setIntensity(light->getIntensity());
341274 globalLight->setAmbientIntensity(light->getAmbientIntensity());
342275 globalLight->setAmbientSphere(light->getAmbientSphere());
343− globalLight->setAmbientMap(light->getAmbientMap());
276+ globalLight->setAmbientMap(light->getAmbientMap());*/
277+}
278+
279+const model::LightPointer& DeferredLightingEffect::getGlobalLight() const {
280+ return _allocatedLights.front();
344281 }
345282
283+
346284 #include <shared/Shapes.h>
347285
348286 model::MeshPointer DeferredLightingEffect::getPointLightMesh() {
@@ -540,7 +478,7 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input · 191 unmodified lines
540478 gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_COLOR1 | gpu::Framebuffer::BUFFER_COLOR2 | gpu::Framebuffer::BUFFER_COLOR3 |
541479 gpu::Framebuffer::BUFFER_DEPTH |
542480 gpu::Framebuffer::BUFFER_STENCIL,
543− vec4(vec3(0), 0), 1.0, 0.0, true);
481+ vec4(vec3(0), 0), 1.0, 1, true);
544482
545483 // For the rest of the rendering, bind the lighting model
546484 batch.setUniformBuffer(LIGHTING_MODEL_BUFFER_SLOT, lightingModel->getParametersBuffer());
@@ -566,10 +504,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, · 19 unmodified lines
566504 {
567505 // Framebuffer copy operations cannot function as multipass stereo operations.
568506 batch.enableStereo(false);
569−
570− // perform deferred lighting, rendering to free fbo
571− auto framebufferCache = DependencyManager::get<FramebufferCache>();
572−
507+
573508 auto textureCache = DependencyManager::get<TextureCache>();
574509 auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>();
575510
@@ -624,8 +559,8 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, · 48 unmodified lines
624559 batch.setResourceTexture(SHADOW_MAP_UNIT, globalShadow->map);
625560 }
626561
627− auto& program = deferredLightingEffect->_shadowMapEnabled ? deferredLightingEffect->_directionalLightShadow : deferredLightingEffect->_directionalLight;
628− LightLocationsPtr locations = deferredLightingEffect->_shadowMapEnabled ? deferredLightingEffect->_directionalLightShadowLocations : deferredLightingEffect->_directionalLightLocations;
562+ auto& program = deferredLightingEffect->_directionalSkyboxLight;
563+ LightLocationsPtr locations = deferredLightingEffect->_directionalSkyboxLightLocations;
629564 const auto& keyLight = deferredLightingEffect->_allocatedLights[deferredLightingEffect->_globalLights.front()];
630565
631566 // Setup the global directional pass pipeline
@@ -772,16 +707,6 @@ void RenderDeferredCleanup::run(const render::RenderContextPointer& renderContex · 140 unmodified lines
772707 batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, nullptr);
773708
774709 }
775−
776− auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>();
777−
778− // End of the Lighting pass
779− if (!deferredLightingEffect->_pointLights.empty()) {
780− deferredLightingEffect->_pointLights.clear();
781− }
782− if (!deferredLightingEffect->_spotLights.empty()) {
783− deferredLightingEffect->_spotLights.clear();
784− }
785710 }
786711
787712 RenderDeferred::RenderDeferred() {
libraries/render-utils/src/DeferredLightingEffect.h+17−26
@@ -20,6 +20,8 @@ · 19 unmodified lines
2020 #include "model/Light.h"
2121 #include "model/Geometry.h"
2222
23+#include <procedural/ProceduralSkybox.h>
24+
2325 #include <render/CullTask.h>
2426
2527 #include "DeferredFrameTransform.h"
@@ -28,11 +30,13 @@ · 2 unmodified lines
2830
2931 #include "LightStage.h"
3032 #include "LightClusters.h"
33+#include "BackgroundStage.h"
3134
3235 #include "SurfaceGeometryPass.h"
3336 #include "SubsurfaceScattering.h"
3437 #include "AmbientOcclusionEffect.h"
3538
39+
3640 class RenderArgs;
3741 struct LightLocations;
3842 using LightLocationsPtr = std::shared_ptr<LightLocations>;
@@ -43,34 +47,30 @@ class DeferredLightingEffect : public Dependency { · 4 unmodified lines
4347
4448 public:
4549 void init();
46−
47− void addLight(const model::LightPointer& light);
48−
49− /// Adds a point light to render for the current frame.
50− void addPointLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(0.0f, 0.0f, 0.0f),
51− float intensity = 0.5f, float falloffRadius = 0.01f);
52−
53− /// Adds a spot light to render for the current frame.
54− void addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f),
55− float intensity = 0.5f, float falloffRadius = 0.01f,
56− const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI);
57−
50+
5851 void setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit);
5952 void unsetKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit);
6053
6154 // update global lighting
6255 void setGlobalLight(const model::LightPointer& light);
56+ const model::LightPointer& getGlobalLight() const;
6357
64− const LightStagePointer getLightStage() { return _lightStage; }
58+ const LightStagePointer& getLightStage() { return _lightStage; }
59+ const BackgroundStagePointer& getBackgroundStage() { return _backgroundStage; }
6560
6661 void setShadowMapEnabled(bool enable) { _shadowMapEnabled = enable; };
6762 void setAmbientOcclusionEnabled(bool enable) { _ambientOcclusionEnabled = enable; }
6863 bool isAmbientOcclusionEnabled() const { return _ambientOcclusionEnabled; }
6964
65+ model::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; }
66+ gpu::TexturePointer getDefaultSkyboxTexture() const { return _defaultSkyboxTexture; }
67+ gpu::TexturePointer getDefaultSkyboxAmbientTexture() const { return _defaultSkyboxAmbientTexture; }
68+
7069 private:
7170 DeferredLightingEffect() = default;
7271
7372 LightStagePointer _lightStage;
73+ BackgroundStagePointer _backgroundStage;
7474
7575 bool _shadowMapEnabled{ false };
7676 bool _ambientOcclusionEnabled{ false };
@@ -82,39 +82,30 @@ private: · 5 unmodified lines
8282
8383 gpu::PipelinePointer _directionalSkyboxLight;
8484 gpu::PipelinePointer _directionalAmbientSphereLight;
85− gpu::PipelinePointer _directionalLight;
8685
8786 gpu::PipelinePointer _directionalSkyboxLightShadow;
8887 gpu::PipelinePointer _directionalAmbientSphereLightShadow;
89− gpu::PipelinePointer _directionalLightShadow;
9088
9189 gpu::PipelinePointer _localLight;
9290 gpu::PipelinePointer _localLightOutline;
9391
94− gpu::PipelinePointer _pointLightBack;
95− gpu::PipelinePointer _pointLightFront;
96− gpu::PipelinePointer _spotLightBack;
97− gpu::PipelinePointer _spotLightFront;
98−
9992 LightLocationsPtr _directionalSkyboxLightLocations;
10093 LightLocationsPtr _directionalAmbientSphereLightLocations;
101− LightLocationsPtr _directionalLightLocations;
10294
10395 LightLocationsPtr _directionalSkyboxLightShadowLocations;
10496 LightLocationsPtr _directionalAmbientSphereLightShadowLocations;
105− LightLocationsPtr _directionalLightShadowLocations;
10697
10798 LightLocationsPtr _localLightLocations;
10899 LightLocationsPtr _localLightOutlineLocations;
109− LightLocationsPtr _pointLightLocations;
110− LightLocationsPtr _spotLightLocations;
111100
112101 using Lights = std::vector<model::LightPointer>;
113102
114103 Lights _allocatedLights;
115104 std::vector<int> _globalLights;
116− std::vector<int> _pointLights;
117− std::vector<int> _spotLights;
105+
106+ model::SkyboxPointer _defaultSkybox { new ProceduralSkybox() };
107+ gpu::TexturePointer _defaultSkyboxTexture;
108+ gpu::TexturePointer _defaultSkyboxAmbientTexture;
118109
119110 friend class LightClusteringPass;
120111 friend class RenderDeferredSetup;
libraries/render-utils/src/GeometryCache.cpp+94−30
@@ -24,6 +24,7 @@ · 23 unmodified lines
2424
2525 #include "TextureCache.h"
2626 #include "RenderUtilsLogging.h"
27+#include "StencilMaskPass.h"
2728
2829 #include "gpu/StandardShaderLib.h"
2930
@@ -68,7 +69,7 @@ std::vector<vec3> polygon() { · 38 unmodified lines
6869 std::vector<vec3> result;
6970 result.reserve(SIDES);
7071 double angleIncrement = 2.0 * M_PI / SIDES;
71− for (size_t i = 0; i < SIDES; ++i) {
72+ for (size_t i = 0; i < SIDES; i++) {
7273 double angle = (double)i * angleIncrement;
7374 result.push_back(vec3{ cos(angle) * 0.5, 0.0, sin(angle) * 0.5 });
7475 }
@@ -171,20 +172,20 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid<N · 96 unmodified lines
171172 vertices.reserve(N * faceCount * 2);
172173 solidIndices.reserve(faceIndexCount * faceCount);
173174
174− for (size_t f = 0; f < faceCount; ++f) {
175+ for (size_t f = 0; f < faceCount; f++) {
175176 const Face<N>& face = shape.faces[f];
176177 // Compute the face normal
177178 vec3 faceNormal = shape.getFaceNormal(f);
178179
179180 // Create the vertices for the face
180− for (Index i = 0; i < N; ++i) {
181+ for (Index i = 0; i < N; i++) {
181182 Index originalIndex = face[i];
182183 vertices.push_back(shape.vertices[originalIndex]);
183184 vertices.push_back(faceNormal);
184185 }
185186
186187 // Create the wire indices for unseen edges
187− for (Index i = 0; i < N; ++i) {
188+ for (Index i = 0; i < N; i++) {
188189 Index a = i;
189190 Index b = (i + 1) % N;
190191 auto token = indexToken(face[a], face[b]);
@@ -196,7 +197,7 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid<N · 5 unmodified lines
196197 }
197198
198199 // Create the solid face indices
199− for (Index i = 0; i < N - 2; ++i) {
200+ for (Index i = 0; i < N - 2; i++) {
200201 solidIndices.push_back(0 + baseVertex);
201202 solidIndices.push_back(i + 1 + baseVertex);
202203 solidIndices.push_back(i + 2 + baseVertex);
@@ -228,10 +229,10 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid · 25 unmodified lines
228229
229230 solidIndices.reserve(faceIndexCount * faceCount);
230231
231− for (size_t f = 0; f < faceCount; ++f) {
232+ for (size_t f = 0; f < faceCount; f++) {
232233 const Face<N>& face = shape.faces[f];
233234 // Create the wire indices for unseen edges
234− for (Index i = 0; i < N; ++i) {
235+ for (Index i = 0; i < N; i++) {
235236 Index a = face[i];
236237 Index b = face[(i + 1) % N];
237238 auto token = indexToken(a, b);
@@ -243,7 +244,7 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid · 5 unmodified lines
243244 }
244245
245246 // Create the solid face indices
246− for (Index i = 0; i < N - 2; ++i) {
247+ for (Index i = 0; i < N - 2; i++) {
247248 solidIndices.push_back(face[i] + baseVertex);
248249 solidIndices.push_back(face[i + 1] + baseVertex);
249250 solidIndices.push_back(face[i + 2] + baseVertex);
@@ -255,23 +256,30 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid · 5 unmodified lines
255256 }
256257
257258 template <uint32_t N>
258−void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) {
259+void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer, bool isConical = false) {
259260 using namespace geometry;
260261 Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE);
261262 VertexVector vertices;
262263 IndexVector solidIndices, wireIndices;
263264
264− // Top and bottom faces
265+ // Top (if not conical) and bottom faces
265266 std::vector<vec3> shape = polygon<N>();
266− for (const vec3& v : shape) {
267− vertices.push_back(vec3(v.x, 0.5f, v.z));
268− vertices.push_back(vec3(0, 1, 0));
267+ if (isConical) {
268+ for (uint32_t i = 0; i < N; i++) {
269+ vertices.push_back(vec3(0.0f, 0.5f, 0.0f));
270+ vertices.push_back(vec3(0.0f, 1.0f, 0.0f));
271+ }
272+ } else {
273+ for (const vec3& v : shape) {
274+ vertices.push_back(vec3(v.x, 0.5f, v.z));
275+ vertices.push_back(vec3(0.0f, 1.0f, 0.0f));
276+ }
269277 }
270278 for (const vec3& v : shape) {
271279 vertices.push_back(vec3(v.x, -0.5f, v.z));
272− vertices.push_back(vec3(0, -1, 0));
280+ vertices.push_back(vec3(0.0f, -1.0f, 0.0f));
273281 }
274− for (uint32_t i = 2; i < N; ++i) {
282+ for (uint32_t i = 2; i < N; i++) {
275283 solidIndices.push_back(baseVertex + 0);
276284 solidIndices.push_back(baseVertex + i);
277285 solidIndices.push_back(baseVertex + i - 1);
@@ -279,7 +287,7 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver · 1 unmodified line
279287 solidIndices.push_back(baseVertex + i + N - 1);
280288 solidIndices.push_back(baseVertex + i + N);
281289 }
282− for (uint32_t i = 1; i <= N; ++i) {
290+ for (uint32_t i = 1; i <= N; i++) {
283291 wireIndices.push_back(baseVertex + (i % N));
284292 wireIndices.push_back(baseVertex + i - 1);
285293 wireIndices.push_back(baseVertex + (i % N) + N);
@@ -289,12 +297,12 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver · 3 unmodified lines
289297 // Now do the sides
290298 baseVertex += 2 * N;
291299
292− for (uint32_t i = 0; i < N; ++i) {
300+ for (uint32_t i = 0; i < N; i++) {
293301 vec3 left = shape[i];
294302 vec3 right = shape[(i + 1) % N];
295303 vec3 normal = glm::normalize(left + right);
296− vec3 topLeft = vec3(left.x, 0.5f, left.z);
297− vec3 topRight = vec3(right.x, 0.5f, right.z);
304+ vec3 topLeft = (isConical ? vec3(0.0f, 0.5f, 0.0f) : vec3(left.x, 0.5f, left.z));
305+ vec3 topRight = (isConical ? vec3(0.0f, 0.5f, 0.0f) : vec3(right.x, 0.5f, right.z));
298306 vec3 bottomLeft = vec3(left.x, -0.5f, left.z);
299307 vec3 bottomRight = vec3(right.x, -0.5f, right.z);
300308
@@ -324,6 +332,41 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver · 23 unmodified lines
324332 shapeData.setupIndices(indexBuffer, solidIndices, wireIndices);
325333 }
326334
335+void drawCircle(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) {
336+ // Draw a circle with radius 1/4th the size of the bounding box
337+ using namespace geometry;
338+
339+ Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE);
340+ VertexVector vertices;
341+ IndexVector solidIndices, wireIndices;
342+ const int NUM_CIRCLE_VERTICES = 64;
343+
344+ std::vector<vec3> shape = polygon<NUM_CIRCLE_VERTICES>();
345+ for (const vec3& v : shape) {
346+ vertices.push_back(vec3(v.x, 0.0f, v.z));
347+ vertices.push_back(vec3(0.0f, 0.0f, 0.0f));
348+ }
349+
350+ for (uint32_t i = 2; i < NUM_CIRCLE_VERTICES; i++) {
351+ solidIndices.push_back(baseVertex + 0);
352+ solidIndices.push_back(baseVertex + i);
353+ solidIndices.push_back(baseVertex + i - 1);
354+ solidIndices.push_back(baseVertex + NUM_CIRCLE_VERTICES);
355+ solidIndices.push_back(baseVertex + i + NUM_CIRCLE_VERTICES - 1);
356+ solidIndices.push_back(baseVertex + i + NUM_CIRCLE_VERTICES);
357+ }
358+
359+ for (uint32_t i = 1; i <= NUM_CIRCLE_VERTICES; i++) {
360+ wireIndices.push_back(baseVertex + (i % NUM_CIRCLE_VERTICES));
361+ wireIndices.push_back(baseVertex + i - 1);
362+ wireIndices.push_back(baseVertex + (i % NUM_CIRCLE_VERTICES) + NUM_CIRCLE_VERTICES);
363+ wireIndices.push_back(baseVertex + (i - 1) + NUM_CIRCLE_VERTICES);
364+ }
365+
366+ shapeData.setupVertices(vertexBuffer, vertices);
367+ shapeData.setupIndices(indexBuffer, solidIndices, wireIndices);
368+}
369+
327370 // FIXME solids need per-face vertices, but smooth shaded
328371 // components do not. Find a way to support using draw elements
329372 // or draw arrays as appropriate
@@ -356,8 +399,8 @@ void GeometryCache::buildShapes() { · 26 unmodified lines
356399 Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE);
357400 ShapeData& shapeData = _shapes[Line];
358401 shapeData.setupVertices(_shapeVertices, VertexVector {
359− vec3(-0.5, 0, 0), vec3(-0.5f, 0, 0),
360− vec3(0.5f, 0, 0), vec3(0.5f, 0, 0)
402+ vec3(-0.5f, 0.0f, 0.0f), vec3(-0.5f, 0.0f, 0.0f),
403+ vec3(0.5f, 0.0f, 0.0f), vec3(0.5f, 0.0f, 0.0f)
361404 });
362405 IndexVector wireIndices;
363406 // Only two indices
@@ -366,20 +409,22 @@ void GeometryCache::buildShapes() { · 2 unmodified lines
366409 shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices);
367410 }
368411
369− // Not implememented yet:
370−
371412 //Triangle,
372413 extrudePolygon<3>(_shapes[Triangle], _shapeVertices, _shapeIndices);
373414 //Hexagon,
374415 extrudePolygon<6>(_shapes[Hexagon], _shapeVertices, _shapeIndices);
375416 //Octagon,
376417 extrudePolygon<8>(_shapes[Octagon], _shapeVertices, _shapeIndices);
377−
378− //Quad,
379− //Circle,
380− //Torus,
381− //Cone,
382418 //Cylinder,
419+ extrudePolygon<64>(_shapes[Cylinder], _shapeVertices, _shapeIndices);
420+ //Cone,
421+ extrudePolygon<64>(_shapes[Cone], _shapeVertices, _shapeIndices, true);
422+ //Circle
423+ drawCircle(_shapes[Circle], _shapeVertices, _shapeIndices);
424+ // Not implememented yet:
425+ //Quad,
426+ //Torus,
427+
383428 }
384429
385430 gpu::Stream::FormatPointer& getSolidStreamFormat() {
@@ -596,7 +641,7 @@ void GeometryCache::updateVertices(int id, const QVector<glm::vec2>& points, con · 210 unmodified lines
596641 auto pointCount = points.size();
597642 auto colorCount = colors.size();
598643 int compactColor = 0;
599− for (auto i = 0; i < pointCount; ++i) {
644+ for (auto i = 0; i < pointCount; i++) {
600645 const auto& point = points[i];
601646 *(vertex++) = point.x;
602647 *(vertex++) = point.y;
@@ -673,7 +718,7 @@ void GeometryCache::updateVertices(int id, const QVector<glm::vec3>& points, con · 70 unmodified lines
673718 const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f);
674719 auto pointCount = points.size();
675720 auto colorCount = colors.size();
676− for (auto i = 0; i < pointCount; ++i) {
721+ for (auto i = 0; i < pointCount; i++) {
677722 const glm::vec3& point = points[i];
678723 if (i < colorCount) {
679724 const glm::vec4& color = colors[i];
@@ -1610,6 +1655,9 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const · 930 unmodified lines
16101655 state->setBlendFunction(true,
16111656 gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
16121657 gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
1658+
1659+ PrepareStencil::testMask(*state);
1660+
16131661 gpu::Shader::BindingSet slotBindings;
16141662 slotBindings.insert(gpu::Shader::Binding(std::string("lineData"), LINE_DATA_SLOT));
16151663 gpu::Shader::makeProgram(*program, slotBindings);
@@ -1663,11 +1711,14 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { · 47 unmodified lines
16631711
16641712 // enable decal blend
16651713 state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
1714+ PrepareStencil::testMask(*state);
16661715
16671716 _standardDrawPipeline = gpu::Pipeline::create(program, state);
16681717
16691718
16701719 auto stateNoBlend = std::make_shared<gpu::State>();
1720+ PrepareStencil::testMaskDrawShape(*state);
1721+
16711722 auto noBlendPS = gpu::StandardShaderLib::getDrawTextureOpaquePS();
16721723 auto programNoBlend = gpu::Shader::createProgram(vs, noBlendPS);
16731724 gpu::Shader::makeProgram((*programNoBlend));
@@ -1690,12 +1741,14 @@ void GeometryCache::useGridPipeline(gpu::Batch& batch, GridBuffer gridBuffer, bo · 16 unmodified lines
16901741
16911742 auto stateLayered = std::make_shared<gpu::State>();
16921743 stateLayered->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
1744+ PrepareStencil::testMask(*stateLayered);
16931745 _gridPipelineLayered = gpu::Pipeline::create(program, stateLayered);
16941746
16951747 auto state = std::make_shared<gpu::State>(stateLayered->getValues());
16961748 const float DEPTH_BIAS = 0.001f;
16971749 state->setDepthBias(DEPTH_BIAS);
16981750 state->setDepthTest(true, false, gpu::LESS_EQUAL);
1751+ PrepareStencil::testMaskDrawShape(*state);
16991752 _gridPipeline = gpu::Pipeline::create(program, state);
17001753 }
17011754
@@ -1773,6 +1826,11 @@ static void buildWebShader(const std::string& vertShaderText, const std::string& · 71 unmodified lines
17731826 state->setBlendFunction(blendEnable,
17741827 gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
17751828 gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
1829+ if (blendEnable) {
1830+ PrepareStencil::testMask(*state);
1831+ } else {
1832+ PrepareStencil::testMaskDrawShape(*state);
1833+ }
17761834
17771835 pipelinePointerOut = gpu::Pipeline::create(shaderPointerOut, state);
17781836 }
@@ -1858,6 +1916,12 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp · 79 unmodified lines
18581916 gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
18591917 gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
18601918
1919+ if (config.isTransparent()) {
1920+ PrepareStencil::testMask(*state);
1921+ } else {
1922+ PrepareStencil::testMaskDrawShape(*state);
1923+ }
1924+
18611925 gpu::ShaderPointer program = (config.isUnlit()) ? _unlitShader : _simpleShader;
18621926 gpu::PipelinePointer pipeline = gpu::Pipeline::create(program, state);
18631927 _simplePrograms.insert(config, pipeline);
libraries/render-utils/src/GeometryCache.h+2−2
@@ -142,8 +142,8 @@ public: · 141 unmodified lines
142142 Dodecahedron,
143143 Icosahedron,
144144 Torus, // not yet implemented
145− Cone, // not yet implemented
146− Cylinder, // not yet implemented
145+ Cone,
146+ Cylinder,
147147 NUM_SHAPES,
148148 };
149149
libraries/render-utils/src/HitEffect.cpp+0−94
@@ -1,94 +0,0 @@
1−//
2−// HitEffect.cpp
3−// interface/src/renderer
4−//
5−// Created by Andrzej Kapolka on 7/14/13.
6−// Copyright 2013 High Fidelity, Inc.
7−//
8−// Distributed under the Apache License, Version 2.0.
9−// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10−//
11−
12−// include this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL
13−
14−
15−#include <glm/gtc/random.hpp>
16−
17−#include <DependencyManager.h>
18−#include <PathUtils.h>
19−#include <SharedUtil.h>
20−
21−#include "AbstractViewStateInterface.h"
22−#include "HitEffect.h"
23−
24−#include "TextureCache.h"
25−#include "DependencyManager.h"
26−#include "ViewFrustum.h"
27−#include "GeometryCache.h"
28−
29−#include <gpu/Context.h>
30−
31−#include "hit_effect_vert.h"
32−#include "hit_effect_frag.h"
33−
34−
35−HitEffect::HitEffect() {
36− _geometryId = DependencyManager::get<GeometryCache>()->allocateID();
37−}
38−
39−HitEffect::~HitEffect() {
40− auto geometryCache = DependencyManager::get<GeometryCache>();
41− if (_geometryId && geometryCache) {
42− geometryCache->releaseID(_geometryId);
43− }
44−}
45−
46−const gpu::PipelinePointer& HitEffect::getHitEffectPipeline() {
47− if (!_hitEffectPipeline) {
48− auto vs = gpu::Shader::createVertex(std::string(hit_effect_vert));
49− auto ps = gpu::Shader::createPixel(std::string(hit_effect_frag));
50− gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
51−
52−
53− gpu::Shader::BindingSet slotBindings;
54− gpu::Shader::makeProgram(*program, slotBindings);
55−
56−
57− gpu::StatePointer state = gpu::StatePointer(new gpu::State());
58−
59− state->setDepthTest(false, false, gpu::LESS_EQUAL);
60−
61− // Blend on transparent
62− state->setBlendFunction(true,
63− gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
64−
65− // Good to go add the brand new pipeline
66− _hitEffectPipeline = gpu::Pipeline::create(program, state);
67− }
68− return _hitEffectPipeline;
69−}
70−
71−void HitEffect::run(const render::RenderContextPointer& renderContext) {
72− assert(renderContext->args);
73− assert(renderContext->args->hasViewFrustum());
74− RenderArgs* args = renderContext->args;
75−
76− gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
77−
78− glm::mat4 projMat;
79− Transform viewMat;
80− args->getViewFrustum().evalProjectionMatrix(projMat);
81− args->getViewFrustum().evalViewTransform(viewMat);
82− batch.setProjectionTransform(projMat);
83− batch.setViewTransform(viewMat);
84− batch.setModelTransform(Transform());
85−
86− batch.setPipeline(getHitEffectPipeline());
87−
88− static const glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f);
89− static const glm::vec2 bottomLeft(-1.0f, -1.0f);
90− static const glm::vec2 topRight(1.0f, 1.0f);
91− DependencyManager::get<GeometryCache>()->renderQuad(batch, bottomLeft, topRight, color, _geometryId);
92− });
93−}
94−
libraries/render-utils/src/HitEffect.h+0−38
@@ -1,38 +0,0 @@
1−//
2−// hitEffect.h
3−// hifi
4−//
5−// Created by eric levin on 7/17/15.
6−//
7−//
8−
9−#ifndef hifi_hitEffect_h
10−#define hifi_hitEffect_h
11−
12−#include <render/DrawTask.h>
13−
14−class HitEffectConfig : public render::Job::Config {
15− Q_OBJECT
16− Q_PROPERTY(bool enabled MEMBER enabled)
17−public:
18− HitEffectConfig() : render::Job::Config(false) {}
19−};
20−
21−class HitEffect {
22−public:
23− using Config = HitEffectConfig;
24− using JobModel = render::Job::Model<HitEffect, Config>;
25−
26− HitEffect();
27− ~HitEffect();
28− void configure(const Config& config) {}
29− void run(const render::RenderContextPointer& renderContext);
30−
31− const gpu::PipelinePointer& getHitEffectPipeline();
32−
33−private:
34− int _geometryId { 0 };
35− gpu::PipelinePointer _hitEffectPipeline;
36−};
37−
38−#endif
libraries/render-utils/src/LightClusters.cpp+2−0
@@ -16,6 +16,8 @@ · 15 unmodified lines
1616
1717 #include <gpu/StandardShaderLib.h>
1818
19+#include "StencilMaskPass.h"
20+
1921 #include "lightClusters_drawGrid_vert.h"
2022 #include "lightClusters_drawGrid_frag.h"
2123
libraries/render-utils/src/LightClusters.h+3−3
@@ -152,9 +152,9 @@ public: · 151 unmodified lines
152152 int numInputLights { 0 };
153153 int numClusteredLights { 0 };
154154
155− void setNumClusteredLightReferences(int numRefs) { numClusteredLightReferences = numRefs; emit dirty(); }
156− void setNumInputLights(int numLights) { numInputLights = numLights; emit dirty(); }
157− void setNumClusteredLights(int numLights) { numClusteredLights = numLights; emit dirty(); }
155+ void setNumClusteredLightReferences(int numRefs) { numClusteredLightReferences = numRefs; }
156+ void setNumInputLights(int numLights) { numInputLights = numLights; }
157+ void setNumClusteredLights(int numLights) { numClusteredLights = numLights; }
158158
159159 int numSceneLights { 0 };
160160 int numFreeSceneLights { 0 };
libraries/render-utils/src/LightPayload.cpp+68−0
@@ -81,3 +81,71 @@ void LightPayload::render(RenderArgs* args) { · 80 unmodified lines
8181 }
8282 }
8383
84+
85+namespace render {
86+ template <> const ItemKey payloadGetKey(const KeyLightPayload::Pointer& payload) {
87+ ItemKey::Builder builder;
88+ builder.withTypeLight();
89+ if (!payload || !payload->isVisible()) {
90+ builder.withInvisible();
91+ }
92+ return builder.build();
93+ }
94+
95+ template <> const Item::Bound payloadGetBound(const KeyLightPayload::Pointer& payload) {
96+ if (payload) {
97+ return payload->editBound();
98+ }
99+ return render::Item::Bound();
100+ }
101+ template <> void payloadRender(const KeyLightPayload::Pointer& payload, RenderArgs* args) {
102+ if (args) {
103+ if (payload) {
104+ payload->render(args);
105+ }
106+ }
107+ }
108+}
109+
110+KeyLightPayload::KeyLightPayload() :
111+_light(std::make_shared<model::Light>())
112+{
113+}
114+
115+
116+KeyLightPayload::~KeyLightPayload() {
117+ if (!LightStage::isIndexInvalid(_index)) {
118+ if (_stage) {
119+ _stage->removeLight(_index);
120+ }
121+ }
122+}
123+
124+void KeyLightPayload::render(RenderArgs* args) {
125+ if (!_stage) {
126+ _stage = DependencyManager::get<DeferredLightingEffect>()->getLightStage();
127+ }
128+ // Do we need to allocate the light in the stage ?
129+ if (LightStage::isIndexInvalid(_index)) {
130+ _index = _stage->addLight(_light);
131+ _needUpdate = false;
132+ }
133+ // Need an update ?
134+ if (_needUpdate) {
135+ _stage->updateLightArrayBuffer(_index);
136+ _needUpdate = false;
137+ }
138+
139+ if (isVisible()) {
140+ // FInally, push the light visible in the frame
141+ _stage->_currentFrame.pushLight(_index, _light->getType());
142+
143+#ifdef WANT_DEBUG
144+ Q_ASSERT(args->_batch);
145+ gpu::Batch& batch = *args->_batch;
146+ batch.setModelTransform(getTransformToCenter());
147+ DependencyManager::get<GeometryCache>()->renderWireSphere(batch, 0.5f, 15, 15, glm::vec4(color, 1.0f));
148+#endif
149+ }
150+}
151+
libraries/render-utils/src/LightPayload.h+38−0
@@ -15,6 +15,7 @@ · 14 unmodified lines
1515 #include <model/Light.h>
1616 #include <render/Item.h>
1717 #include "LightStage.h"
18+#include "TextureCache.h"
1819
1920 class LightPayload {
2021 public:
@@ -46,4 +47,41 @@ namespace render { · 25 unmodified lines
4647 template <> void payloadRender(const LightPayload::Pointer& payload, RenderArgs* args);
4748 }
4849
50+class KeyLightPayload {
51+public:
52+ using Payload = render::Payload<KeyLightPayload>;
53+ using Pointer = Payload::DataPointer;
54+
55+ KeyLightPayload();
56+ ~KeyLightPayload();
57+ void render(RenderArgs* args);
58+
59+ model::LightPointer editLight() { _needUpdate = true; return _light; }
60+ render::Item::Bound& editBound() { _needUpdate = true; return _bound; }
61+
62+ void setVisible(bool visible) { _isVisible = visible; }
63+ bool isVisible() const { return _isVisible; }
64+
65+
66+ // More attributes used for rendering:
67+ NetworkTexturePointer _ambientTexture;
68+ QString _ambientTextureURL;
69+ bool _pendingAmbientTexture { false };
70+ bool _validAmbientTextureURL { false };
71+
72+protected:
73+ model::LightPointer _light;
74+ render::Item::Bound _bound;
75+ LightStagePointer _stage;
76+ LightStage::Index _index { LightStage::INVALID_INDEX };
77+ bool _needUpdate { true };
78+ bool _isVisible { true };
79+};
80+
81+namespace render {
82+ template <> const ItemKey payloadGetKey(const KeyLightPayload::Pointer& payload);
83+ template <> const Item::Bound payloadGetBound(const KeyLightPayload::Pointer& payload);
84+ template <> void payloadRender(const KeyLightPayload::Pointer& payload, RenderArgs* args);
85+}
86+
4987 #endif
libraries/render-utils/src/LightStage.h+8−3
@@ -77,7 +77,6 @@ public: · 76 unmodified lines
7777 };
7878 using Descs = std::vector<Desc>;
7979
80−
8180 Index findLight(const LightPointer& light) const;
8281 Index addLight(const LightPointer& light);
8382
@@ -118,19 +117,25 @@ public: · 34 unmodified lines
118117 public:
119118 Frame() {}
120119
121− void clear() { _pointLights.clear(); _spotLights.clear(); }
120+ void clear() { _pointLights.clear(); _spotLights.clear(); _sunLights.clear(); _ambientLights.clear(); }
122121 void pushLight(LightStage::Index index, model::Light::Type type) {
123122 switch (type) {
124123 case model::Light::POINT: { pushPointLight(index); break; }
125124 case model::Light::SPOT: { pushSpotLight(index); break; }
125+ case model::Light::SUN: { pushSunLight(index); break; }
126+ case model::Light::AMBIENT: { pushAmbientLight(index); break; }
126127 default: { break; }
127128 }
128129 }
129130 void pushPointLight(LightStage::Index index) { _pointLights.emplace_back(index); }
130131 void pushSpotLight(LightStage::Index index) { _spotLights.emplace_back(index); }
131−
132+ void pushSunLight(LightStage::Index index) { _sunLights.emplace_back(index); }
133+ void pushAmbientLight(LightStage::Index index) { _ambientLights.emplace_back(index); }
134+
132135 LightStage::LightIndices _pointLights;
133136 LightStage::LightIndices _spotLights;
137+ LightStage::LightIndices _sunLights;
138+ LightStage::LightIndices _ambientLights;
134139 };
135140
136141 Frame _currentFrame;
libraries/render-utils/src/Model.cpp+2−2
@@ -332,7 +332,7 @@ void Model::initJointStates() { · 331 unmodified lines
332332
333333 bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
334334 BoxFace& face, glm::vec3& surfaceNormal,
335− QString& extraInfo, bool pickAgainstTriangles) {
335+ QString& extraInfo, bool pickAgainstTriangles, bool allowBackface) {
336336
337337 bool intersectedSomething = false;
338338
@@ -381,7 +381,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g · 42 unmodified lines
381381 float triangleSetDistance = 0.0f;
382382 BoxFace triangleSetFace;
383383 glm::vec3 triangleSetNormal;
384− if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles)) {
384+ if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles, allowBackface)) {
385385
386386 glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance);
387387 glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f));
libraries/render-utils/src/Model.h+1−1
@@ -156,7 +156,7 @@ public: · 155 unmodified lines
156156
157157 bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
158158 BoxFace& face, glm::vec3& surfaceNormal,
159− QString& extraInfo, bool pickAgainstTriangles = false);
159+ QString& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false);
160160
161161 void setOffset(const glm::vec3& offset);
162162 const glm::vec3& getOffset() const { return _offset; }
libraries/render-utils/src/RenderDeferredTask.cpp+15−95
@@ -27,12 +27,12 @@ · 26 unmodified lines
2727 #include <render/BlurTask.h>
2828
2929 #include "LightingModel.h"
30+#include "StencilMaskPass.h"
3031 #include "DebugDeferredBuffer.h"
3132 #include "DeferredFramebuffer.h"
3233 #include "DeferredLightingEffect.h"
3334 #include "SurfaceGeometryPass.h"
3435 #include "FramebufferCache.h"
35−#include "HitEffect.h"
3636 #include "TextureCache.h"
3737 #include "ZoneRenderer.h"
3838
@@ -43,8 +43,6 @@ · 4 unmodified lines
4343
4444 #include <gpu/StandardShaderLib.h>
4545
46−#include "drawOpaqueStencil_frag.h"
47−
4846
4947 using namespace render;
5048 extern void initOverlay3DPipelines(render::ShapePlumber& plumber);
@@ -75,7 +73,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 24 unmodified lines
7573 const auto deferredFrameTransform = task.addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform");
7674 const auto lightingModel = task.addJob<MakeLightingModel>("LightingModel");
7775
78−
7976 // GPU jobs: Start preparing the primary, deferred and lighting buffer
8077 const auto primaryFramebuffer = task.addJob<PreparePrimaryFramebuffer>("PreparePrimaryBuffer");
8178
@@ -86,13 +83,13 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 4 unmodified lines
8683 const auto deferredFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(0);
8784 const auto lightingFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(1);
8885
86+ // draw a stencil mask in hidden regions of the framebuffer.
87+ task.addJob<PrepareStencil>("PrepareStencil", primaryFramebuffer);
88+
8989 // Render opaque objects in DeferredBuffer
9090 const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).hasVarying();
9191 task.addJob<DrawStateSortDeferred>("DrawOpaqueDeferred", opaqueInputs, shapePlumber);
9292
93− // Once opaque is all rendered create stencil background
94− task.addJob<DrawStencilDeferred>("DrawOpaqueStencil", deferredFramebuffer);
95−
9693 task.addJob<EndGPURangeTimer>("OpaqueRangeTimer", opaqueRangeTimer);
9794
9895
@@ -124,6 +121,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 25 unmodified lines
124121 // Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now.
125122 task.addJob<DrawLight>("DrawLight", lights);
126123
124+ // Filter zones from the general metas bucket
125+ const auto zones = task.addJob<ZoneRendererTask>("ZoneRenderer", metas);
126+
127127 // Light Clustering
128128 // Create the cluster grid of lights, cpu job for now
129129 const auto lightClusteringPassInputs = LightClusteringPass::Inputs(deferredFrameTransform, lightingModel, linearDepthTarget).hasVarying();
@@ -136,11 +136,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 6 unmodified lines
136136
137137 task.addJob<RenderDeferred>("RenderDeferred", deferredLightingInputs);
138138
139− // Use Stencil and draw background in Lighting buffer to complete filling in the opaque
140− const auto backgroundInputs = DrawBackgroundDeferred::Inputs(background, lightingModel).hasVarying();
141− task.addJob<DrawBackgroundDeferred>("DrawBackgroundDeferred", backgroundInputs);
142−
143−
139+ // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job
140+ task.addJob<DrawBackgroundStage>("DrawBackgroundDeferred", lightingModel);
141+
144142 // Render transparent objects forward in LightingBuffer
145143 const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).hasVarying();
146144 task.addJob<DrawDeferred>("DrawTransparentDeferred", transparentsInputs, shapePlumber);
@@ -162,7 +160,8 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 15 unmodified lines
162160 task.addJob<DrawBounds>("DrawOpaqueBounds", opaques);
163161 task.addJob<DrawBounds>("DrawTransparentBounds", transparents);
164162
165− task.addJob<ZoneRendererTask>("ZoneRenderer", opaques);
163+ task.addJob<DrawBounds>("DrawLightBounds", lights);
164+ task.addJob<DrawBounds>("DrawZones", zones);
166165 }
167166
168167 // Overlays
@@ -202,6 +201,8 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 33 unmodified lines
202201 auto statusIconMap = DependencyManager::get<TextureCache>()->getImageTexture(iconMapPath, image::TextureUsage::STRICT_TEXTURE);
203202 task.addJob<DrawStatus>("DrawStatus", opaques, DrawStatus(statusIconMap));
204203 }
204+
205+ task.addJob<DebugZoneLighting>("DrawZoneStack", deferredFrameTransform);
205206 }
206207
207208
@@ -384,88 +385,6 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& · 176 unmodified lines
384385 }
385386 }
386387
387−
388−gpu::PipelinePointer DrawStencilDeferred::getOpaquePipeline() {
389− if (!_opaquePipeline) {
390− const gpu::int8 STENCIL_OPAQUE = 1;
391− auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS();
392− auto ps = gpu::Shader::createPixel(std::string(drawOpaqueStencil_frag));
393− auto program = gpu::Shader::createProgram(vs, ps);
394− gpu::Shader::makeProgram((*program));
395−
396− auto state = std::make_shared<gpu::State>();
397− state->setDepthTest(true, false, gpu::LESS_EQUAL);
398− state->setStencilTest(true, 0xFF, gpu::State::StencilTest(STENCIL_OPAQUE, 0xFF, gpu::ALWAYS, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_KEEP));
399− state->setColorWriteMask(0);
400−
401− _opaquePipeline = gpu::Pipeline::create(program, state);
402− }
403− return _opaquePipeline;
404−}
405−
406−void DrawStencilDeferred::run(const RenderContextPointer& renderContext, const DeferredFramebufferPointer& deferredFramebuffer) {
407− assert(renderContext->args);
408− assert(renderContext->args->hasViewFrustum());
409−
410− // from the touched pixel generate the stencil buffer
411− RenderArgs* args = renderContext->args;
412− doInBatch(args->_context, [&](gpu::Batch& batch) {
413− args->_batch = &batch;
414−
415− auto deferredFboColorDepthStencil = deferredFramebuffer->getDeferredFramebufferDepthColor();
416−
417−
418− batch.enableStereo(false);
419−
420− batch.setFramebuffer(deferredFboColorDepthStencil);
421− batch.setViewportTransform(args->_viewport);
422− batch.setStateScissorRect(args->_viewport);
423−
424− batch.setPipeline(getOpaquePipeline());
425−
426− batch.draw(gpu::TRIANGLE_STRIP, 4);
427− batch.setResourceTexture(0, nullptr);
428−
429− });
430− args->_batch = nullptr;
431−}
432−
433−void DrawBackgroundDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs) {
434− assert(renderContext->args);
435− assert(renderContext->args->hasViewFrustum());
436−
437− const auto& inItems = inputs.get0();
438− const auto& lightingModel = inputs.get1();
439− if (!lightingModel->isBackgroundEnabled()) {
440− return;
441− }
442−
443− RenderArgs* args = renderContext->args;
444− doInBatch(args->_context, [&](gpu::Batch& batch) {
445− args->_batch = &batch;
446− // _gpuTimer.begin(batch);
447−
448− batch.enableSkybox(true);
449−
450− batch.setViewportTransform(args->_viewport);
451− batch.setStateScissorRect(args->_viewport);
452−
453− glm::mat4 projMat;
454− Transform viewMat;
455− args->getViewFrustum().evalProjectionMatrix(projMat);
456− args->getViewFrustum().evalViewTransform(viewMat);
457−
458− batch.setProjectionTransform(projMat);
459− batch.setViewTransform(viewMat);
460−
461− renderItems(renderContext, inItems);
462− // _gpuTimer.end(batch);
463− });
464− args->_batch = nullptr;
465−
466− // std::static_pointer_cast<Config>(renderContext->jobConfig)->gpuTime = _gpuTimer.getAverage();
467−}
468−
469388 void Blit::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) {
470389 assert(renderContext->args);
471390 assert(renderContext->args->_context);
@@ -535,3 +454,4 @@ void Blit::run(const RenderContextPointer& renderContext, const gpu::Framebuffer · 63 unmodified lines
535454 }
536455 });
537456 }
457+
libraries/render-utils/src/RenderDeferredTask.h+0−29
@@ -120,35 +120,6 @@ protected: · 119 unmodified lines
120120 bool _stateSort;
121121 };
122122
123−class DeferredFramebuffer;
124−class DrawStencilDeferred {
125−public:
126− using JobModel = render::Job::ModelI<DrawStencilDeferred, std::shared_ptr<DeferredFramebuffer>>;
127−
128− void run(const render::RenderContextPointer& renderContext, const std::shared_ptr<DeferredFramebuffer>& deferredFramebuffer);
129−
130−protected:
131− gpu::PipelinePointer _opaquePipeline;
132−
133− gpu::PipelinePointer getOpaquePipeline();
134−};
135−
136−using DrawBackgroundDeferredConfig = render::GPUJobConfig;
137−
138−class DrawBackgroundDeferred {
139−public:
140− using Inputs = render::VaryingSet2 <render::ItemBounds, LightingModelPointer>;
141−
142− using Config = DrawBackgroundDeferredConfig;
143− using JobModel = render::Job::ModelI<DrawBackgroundDeferred, Inputs, Config>;
144−
145− void configure(const Config& config) {}
146− void run(const render::RenderContextPointer& renderContext, const Inputs& inputs);
147−
148−protected:
149− gpu::RangeTimerPointer _gpuTimer;
150−};
151−
152123 class DrawOverlay3DConfig : public render::Job::Config {
153124 Q_OBJECT
154125 Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged)
libraries/render-utils/src/RenderPipelines.cpp+2−0
@@ -15,6 +15,7 @@ · 14 unmodified lines
1515 #include <gpu/Context.h>
1616 #include <gpu/StandardShaderLib.h>
1717
18+#include "StencilMaskPass.h"
1819 #include "DeferredLightingEffect.h"
1920 #include "TextureCache.h"
2021 #include "render/DrawTask.h"
@@ -330,6 +331,7 @@ void addPlumberPipeline(ShapePlumber& plumber, · 309 unmodified lines
330331 bool isWireframed = (i & 4);
331332
332333 auto state = std::make_shared<gpu::State>();
334+ PrepareStencil::testMaskDrawShape(*state);
333335
334336 // Depth test depends on transparency
335337 state->setDepthTest(true, !key.isTranslucent(), gpu::LESS_EQUAL);
libraries/render-utils/src/RenderShadowTask.cpp+3−2
@@ -48,6 +48,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, · 47 unmodified lines
4848 RenderArgs* args = renderContext->args;
4949 gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
5050 args->_batch = &batch;
51+ batch.enableStereo(false);
5152
5253 glm::ivec4 viewport{0, 0, fbo->getWidth(), fbo->getHeight()};
5354 batch.setViewportTransform(viewport);
@@ -114,7 +115,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende · 60 unmodified lines
114115 skinProgram, state);
115116 }
116117
117− const auto cachedMode = task.addJob<RenderShadowSetup>("Setup");
118+ const auto cachedMode = task.addJob<RenderShadowSetup>("ShadowSetup");
118119
119120 // CPU jobs:
120121 // Fetch and cull the items from the scene
@@ -129,7 +130,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende · 8 unmodified lines
129130 // GPU jobs: Render to shadow map
130131 task.addJob<RenderShadowMap>("RenderShadowMap", sortedShapes, shapePlumber);
131132
132− task.addJob<RenderShadowTeardown>("Teardown", cachedMode);
133+ task.addJob<RenderShadowTeardown>("ShadowTeardown", cachedMode);
133134 }
134135
135136 void RenderShadowTask::configure(const Config& configuration) {
libraries/render-utils/src/RenderViewTask.cpp+33−0
@@ -0,0 +1,33 @@
1+//
2+// RenderViewTask.cpp
3+// render-utils/src/
4+//
5+// Created by Sam Gateau on 5/25/2017.
6+// Copyright 2017 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+#include "RenderViewTask.h"
12+
13+#include "RenderShadowTask.h"
14+#include "RenderDeferredTask.h"
15+#include "RenderForwardTask.h"
16+
17+
18+
19+void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred) {
20+ // auto items = input.get<Input>();
21+
22+ task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor);
23+
24+ const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
25+ assert(items.canCast<RenderFetchCullSortTask::Output>());
26+
27+ if (isDeferred) {
28+ task.addJob<RenderDeferredTask>("RenderDeferredTask", items);
29+ } else {
30+ task.addJob<RenderForwardTask>("Forward", items);
31+ }
32+}
33+
libraries/render-utils/src/RenderViewTask.h+31−0
@@ -0,0 +1,31 @@
1+//
2+// RenderViewTask.h
3+// render-utils/src/
4+//
5+// Created by Sam Gateau on 5/25/2017.
6+// Copyright 2017 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+#pragma once
12+#ifndef hifi_RenderViewTask_h
13+#define hifi_RenderViewTask_h
14+
15+#include <render/Engine.h>
16+#include <render/RenderFetchCullSortTask.h>
17+
18+
19+class RenderViewTask {
20+public:
21+ using Input = RenderFetchCullSortTask::Output;
22+ using JobModel = render::Task::ModelI<RenderViewTask, Input>;
23+
24+ RenderViewTask() {}
25+
26+ void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred);
27+
28+};
29+
30+
31+#endif // hifi_RenderViewTask_h
libraries/render-utils/src/StencilMaskPass.cpp+126−0
@@ -0,0 +1,126 @@
1+//
2+// StencilMaskPass.cpp
3+// render-utils/src/
4+//
5+// Created by Sam Gateau on 5/31/17.
6+// Copyright 2016 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+
12+#include "StencilMaskPass.h"
13+
14+#include <RenderArgs.h>
15+#include <ViewFrustum.h>
16+#include <gpu/Context.h>
17+
18+
19+#include <gpu/StandardShaderLib.h>
20+
21+#include "stencil_drawMask_frag.h"
22+
23+using namespace render;
24+
25+void PrepareStencil::configure(const Config& config) {
26+ _maskMode = config.maskMode;
27+ _forceDraw = config.forceDraw;
28+}
29+
30+model::MeshPointer PrepareStencil::getMesh() {
31+ if (!_mesh) {
32+
33+ std::vector<glm::vec3> vertices {
34+ { -1.0f, -1.0f, 0.0f }, { -1.0f, 0.0f, 0.0f },
35+ { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f },
36+ { 1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f },
37+ { 1.0f, -1.0f, 0.0f }, { 0.0f, -1.0f, 0.0f } };
38+
39+ std::vector<uint32_t> indices { 0, 7, 1, 1, 3, 2, 3, 5, 4, 5, 7, 6 };
40+ _mesh = model::Mesh::createIndexedTriangles_P3F((uint32_t) vertices.size(), (uint32_t) indices.size(), vertices.data(), indices.data());
41+ }
42+ return _mesh;
43+}
44+
45+gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() {
46+ if (!_meshStencilPipeline) {
47+ auto vs = gpu::StandardShaderLib::getDrawVertexPositionVS();
48+ auto ps = gpu::StandardShaderLib::getDrawNadaPS();
49+ auto program = gpu::Shader::createProgram(vs, ps);
50+ gpu::Shader::makeProgram((*program));
51+
52+ auto state = std::make_shared<gpu::State>();
53+ drawMask(*state);
54+ state->setColorWriteMask(0);
55+
56+ _meshStencilPipeline = gpu::Pipeline::create(program, state);
57+ }
58+ return _meshStencilPipeline;
59+}
60+
61+gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() {
62+ if (!_paintStencilPipeline) {
63+ auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS();
64+ auto ps = gpu::Shader::createPixel(std::string(stencil_drawMask_frag));
65+ auto program = gpu::Shader::createProgram(vs, ps);
66+ gpu::Shader::makeProgram((*program));
67+
68+ auto state = std::make_shared<gpu::State>();
69+ drawMask(*state);
70+ state->setColorWriteMask(0);
71+
72+ _paintStencilPipeline = gpu::Pipeline::create(program, state);
73+ }
74+ return _paintStencilPipeline;
75+}
76+
77+void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) {
78+ RenderArgs* args = renderContext->args;
79+
80+ // Only draw the stencil mask if in HMD mode or not forced.
81+ if (!_forceDraw && (args->_displayMode != RenderArgs::STEREO_HMD)) {
82+ return;
83+ }
84+
85+ doInBatch(args->_context, [&](gpu::Batch& batch) {
86+ batch.enableStereo(false);
87+
88+ batch.setViewportTransform(args->_viewport);
89+
90+ if (_maskMode < 0) {
91+ batch.setPipeline(getMeshStencilPipeline());
92+
93+ auto mesh = getMesh();
94+ batch.setIndexBuffer(mesh->getIndexBuffer());
95+ batch.setInputFormat((mesh->getVertexFormat()));
96+ batch.setInputStream(0, mesh->getVertexStream());
97+
98+ // Draw
99+ auto part = mesh->getPartBuffer().get<model::Mesh::Part>(0);
100+ batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
101+ } else {
102+ batch.setPipeline(getPaintStencilPipeline());
103+ batch.draw(gpu::TRIANGLE_STRIP, 4);
104+ }
105+ });
106+}
107+
108+void PrepareStencil::drawMask(gpu::State& state) {
109+ state.setStencilTest(true, 0xFF, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::ALWAYS, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE));
110+}
111+
112+void PrepareStencil::testMask(gpu::State& state) {
113+ state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
114+}
115+
116+void PrepareStencil::testBackground(gpu::State& state) {
117+ state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_BACKGROUND, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
118+}
119+
120+void PrepareStencil::testMaskDrawShape(gpu::State& state) {
121+ state.setStencilTest(true, 0xFF, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_ZERO));
122+}
123+
124+void PrepareStencil::testShape(gpu::State& state) {
125+ state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_SHAPE, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
126+}
libraries/render-utils/src/StencilMaskPass.h+71−0
@@ -0,0 +1,71 @@
1+//
2+// StencilMaskPass.h
3+// render-utils/src/
4+//
5+// Created by Sam Gateau on 5/31/17.
6+// Copyright 20154 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+#pragma once
12+#ifndef hifi_StencilMaskPass_h
13+#define hifi_StencilMaskPass_h
14+
15+#include <render/Engine.h>
16+#include <gpu/Pipeline.h>
17+#include <model/Geometry.h>
18+
19+class PrepareStencilConfig : public render::Job::Config {
20+ Q_OBJECT
21+ Q_PROPERTY(int maskMode MEMBER maskMode NOTIFY dirty)
22+ Q_PROPERTY(bool forceDraw MEMBER forceDraw NOTIFY dirty)
23+
24+public:
25+ PrepareStencilConfig(bool enabled = true) : JobConfig(enabled) {}
26+
27+ int maskMode { 0 };
28+ bool forceDraw { false };
29+
30+signals:
31+ void dirty();
32+};
33+
34+class PrepareStencil {
35+public:
36+ using Config = PrepareStencilConfig;
37+
38+ using JobModel = render::Job::ModelI<PrepareStencil, gpu::FramebufferPointer, Config>;
39+
40+ void configure(const Config& config);
41+
42+ void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& dstFramebuffer);
43+
44+ static const gpu::int8 STENCIL_MASK = 2;
45+ static const gpu::int8 STENCIL_BACKGROUND = 1;
46+ static const gpu::int8 STENCIL_SHAPE = 0;
47+
48+
49+ static void drawMask(gpu::State& state);
50+ static void testMask(gpu::State& state);
51+ static void testBackground(gpu::State& state);
52+ static void testMaskDrawShape(gpu::State& state);
53+ static void testShape(gpu::State& state);
54+
55+
56+private:
57+ gpu::PipelinePointer _meshStencilPipeline;
58+ gpu::PipelinePointer getMeshStencilPipeline();
59+
60+ gpu::PipelinePointer _paintStencilPipeline;
61+ gpu::PipelinePointer getPaintStencilPipeline();
62+
63+ model::MeshPointer _mesh;
64+ model::MeshPointer getMesh();
65+
66+ int _maskMode { 0 };
67+ bool _forceDraw { false };
68+};
69+
70+
71+#endif // hifi_StencilMaskPass_h
libraries/render-utils/src/SurfaceGeometryPass.cpp+4−3
@@ -14,7 +14,7 @@ · 13 unmodified lines
1414
1515 #include <gpu/Context.h>
1616 #include <gpu/StandardShaderLib.h>
17−
17+#include "StencilMaskPass.h"
1818
1919 const int DepthLinearPass_FrameTransformSlot = 0;
2020 const int DepthLinearPass_DepthMapSlot = 0;
@@ -224,7 +224,7 @@ const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline() { · 203 unmodified lines
224224 gpu::StatePointer state = gpu::StatePointer(new gpu::State());
225225
226226 // Stencil test the curvature pass for objects pixels only, not the background
227− state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
227+ PrepareStencil::testShape(*state);
228228
229229 state->setColorWriteMask(true, false, false, false);
230230
@@ -250,6 +250,7 @@ const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline() { · 19 unmodified lines
250250
251251
252252 gpu::StatePointer state = gpu::StatePointer(new gpu::State());
253+ PrepareStencil::testShape(*state);
253254
254255 state->setColorWriteMask(true, true, true, false);
255256
@@ -554,7 +555,7 @@ const gpu::PipelinePointer& SurfaceGeometryPass::getCurvaturePipeline() { · 298 unmodified lines
554555
555556 #ifdef USE_STENCIL_TEST
556557 // Stencil test the curvature pass for objects pixels only, not the background
557− state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
558+ PrepareStencil::testShape(*state);
558559 #endif
559560 // Good to go add the brand new pipeline
560561 _curvaturePipeline = gpu::Pipeline::create(program, state);
libraries/render-utils/src/ToneMappingEffect.cpp+1−1
@@ -15,7 +15,7 @@ · 14 unmodified lines
1515 #include <gpu/StandardShaderLib.h>
1616
1717 #include <RenderArgs.h>
18−
18+#include "StencilMaskPass.h"
1919 #include "FramebufferCache.h"
2020
2121 #include "toneMapping_frag.h"
libraries/render-utils/src/ZoneRenderer.cpp+188−3
@@ -10,18 +10,203 @@ · 9 unmodified lines
1010 //
1111 #include "ZoneRenderer.h"
1212
13+
14+#include <gpu/Context.h>
15+#include <gpu/StandardShaderLib.h>
16+
1317 #include <render/FilterTask.h>
1418 #include <render/DrawTask.h>
1519
20+#include "StencilMaskPass.h"
21+#include "DeferredLightingEffect.h"
22+
23+#include "zone_drawKeyLight_frag.h"
24+#include "zone_drawAmbient_frag.h"
25+#include "zone_drawSkybox_frag.h"
26+
27+
1628 using namespace render;
1729
30+class SetupZones {
31+public:
32+ using Inputs = render::ItemBounds;
33+ using JobModel = render::Job::ModelI<SetupZones, Inputs>;
34+
35+ SetupZones() {}
36+
37+ void run(const RenderContextPointer& context, const Inputs& inputs);
38+
39+protected:
40+};
41+
1842 const Selection::Name ZoneRendererTask::ZONES_SELECTION { "RankedZones" };
1943
2044 void ZoneRendererTask::build(JobModel& task, const Varying& input, Varying& ouput) {
21−
45+ // Filter out the sorted list of zones
2246 const auto zoneItems = task.addJob<render::SelectSortItems>("FilterZones", input, ZONES_SELECTION.c_str());
2347
24− // just draw them...
25− task.addJob<DrawBounds>("DrawZones", zoneItems);
48+ // just setup the current zone env
49+ task.addJob<SetupZones>("SetupZones", zoneItems);
50+
51+ ouput = zoneItems;
52+}
53+
54+void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) {
55+
56+ auto backgroundStage = DependencyManager::get<DeferredLightingEffect>()->getBackgroundStage();
57+ backgroundStage->_currentFrame.clear();
58+
59+ // call render in the correct order first...
60+ render::renderItems(context, inputs);
61+
62+}
63+
64+const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() {
65+ if (!_keyLightPipeline) {
66+ auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS();
67+ auto ps = gpu::Shader::createPixel(std::string(zone_drawKeyLight_frag));
68+ gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
69+
70+ gpu::Shader::BindingSet slotBindings;
71+ slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ZONE_DEFERRED_TRANSFORM_BUFFER));
72+ slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), ZONE_KEYLIGHT_BUFFER));
73+
74+ gpu::Shader::makeProgram(*program, slotBindings);
75+
76+ gpu::StatePointer state = gpu::StatePointer(new gpu::State());
77+
78+ PrepareStencil::testMask(*state);
79+ state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
80+ _keyLightPipeline = gpu::Pipeline::create(program, state);
81+ }
82+ return _keyLightPipeline;
83+}
84+
85+const gpu::PipelinePointer& DebugZoneLighting::getAmbientPipeline() {
86+ if (!_ambientPipeline) {
87+ auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS();
88+ auto ps = gpu::Shader::createPixel(std::string(zone_drawAmbient_frag));
89+ gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
90+
91+ gpu::Shader::BindingSet slotBindings;
92+ slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ZONE_DEFERRED_TRANSFORM_BUFFER));
93+ slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), ZONE_AMBIENT_BUFFER));
94+ slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), ZONE_AMBIENT_MAP));
95+
96+ gpu::Shader::makeProgram(*program, slotBindings);
97+
98+ gpu::StatePointer state = gpu::StatePointer(new gpu::State());
99+
100+ PrepareStencil::testMask(*state);
101+ state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
102+ _ambientPipeline = gpu::Pipeline::create(program, state);
103+ }
104+ return _ambientPipeline;
26105 }
106+const gpu::PipelinePointer& DebugZoneLighting::getBackgroundPipeline() {
107+ if (!_backgroundPipeline) {
108+ auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS();
109+ auto ps = gpu::Shader::createPixel(std::string(zone_drawSkybox_frag));
110+ gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
111+
112+ gpu::Shader::BindingSet slotBindings;
113+ slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ZONE_DEFERRED_TRANSFORM_BUFFER));
114+ slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), ZONE_SKYBOX_MAP));
115+ slotBindings.insert(gpu::Shader::Binding(std::string("skyboxBuffer"), ZONE_SKYBOX_BUFFER));
116+
117+ gpu::Shader::makeProgram(*program, slotBindings);
118+
119+ gpu::StatePointer state = gpu::StatePointer(new gpu::State());
120+
121+ PrepareStencil::testMask(*state);
122+ state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
123+ _backgroundPipeline = gpu::Pipeline::create(program, state);
124+ }
125+ return _backgroundPipeline;
126+}
127+
128+void DebugZoneLighting::run(const render::RenderContextPointer& context, const Inputs& inputs) {
129+ RenderArgs* args = context->args;
130+
131+ auto deferredTransform = inputs;
132+
133+ auto lightStage = DependencyManager::get<DeferredLightingEffect>()->getLightStage();
134+ std::vector<model::LightPointer> keyLightStack;
135+ if (lightStage && lightStage->_currentFrame._sunLights.size()) {
136+ for (auto index : lightStage->_currentFrame._sunLights) {
137+ keyLightStack.push_back(lightStage->getLight(index));
138+ }
139+ }
140+ keyLightStack.push_back(DependencyManager::get<DeferredLightingEffect>()->getGlobalLight());
27141
142+ std::vector<model::LightPointer> ambientLightStack;
143+ if (lightStage && lightStage->_currentFrame._ambientLights.size()) {
144+ for (auto index : lightStage->_currentFrame._ambientLights) {
145+ ambientLightStack.push_back(lightStage->getLight(index));
146+ }
147+ }
148+ ambientLightStack.push_back(DependencyManager::get<DeferredLightingEffect>()->getGlobalLight());
149+
150+
151+ auto backgroundStage = DependencyManager::get<DeferredLightingEffect>()->getBackgroundStage();
152+ std::vector<model::SkyboxPointer> skyboxStack;
153+ if (backgroundStage && backgroundStage->_currentFrame._backgrounds.size()) {
154+ for (auto index : backgroundStage->_currentFrame._backgrounds) {
155+ auto background = backgroundStage->getBackground(index);
156+ if (background) {
157+ skyboxStack.push_back(background->getSkybox());
158+ }
159+ }
160+ }
161+ skyboxStack.push_back(DependencyManager::get<DeferredLightingEffect>()->getDefaultSkybox());
162+
163+
164+ gpu::doInBatch(args->_context, [=](gpu::Batch& batch) {
165+
166+ batch.setViewportTransform(args->_viewport);
167+ auto viewFrustum = args->getViewFrustum();
168+ batch.setProjectionTransform(viewFrustum.getProjection());
169+ batch.resetViewTransform();
170+
171+ Transform model;
172+
173+ batch.setUniformBuffer(ZONE_DEFERRED_TRANSFORM_BUFFER, deferredTransform->getFrameTransformBuffer());
174+
175+ batch.setPipeline(getKeyLightPipeline());
176+ auto numKeys = (int) keyLightStack.size();
177+ for (int i = numKeys - 1; i >= 0; i--) {
178+ model.setTranslation(glm::vec3(-4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0)));
179+ batch.setModelTransform(model);
180+ if (keyLightStack[i]) {
181+ batch.setUniformBuffer(ZONE_KEYLIGHT_BUFFER, keyLightStack[i]->getLightSchemaBuffer());
182+ batch.draw(gpu::TRIANGLE_STRIP, 4);
183+ }
184+ }
185+
186+ batch.setPipeline(getAmbientPipeline());
187+ auto numAmbients = (int) ambientLightStack.size();
188+ for (int i = numAmbients - 1; i >= 0; i--) {
189+ model.setTranslation(glm::vec3(0.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0)));
190+ batch.setModelTransform(model);
191+ if (ambientLightStack[i]) {
192+ batch.setUniformBuffer(ZONE_AMBIENT_BUFFER, ambientLightStack[i]->getAmbientSchemaBuffer());
193+ if (ambientLightStack[i]->getAmbientMap()) {
194+ batch.setResourceTexture(ZONE_AMBIENT_MAP, ambientLightStack[i]->getAmbientMap());
195+ }
196+ batch.draw(gpu::TRIANGLE_STRIP, 4);
197+ }
198+ }
199+
200+ batch.setPipeline(getBackgroundPipeline());
201+ auto numBackgrounds = (int) skyboxStack.size();
202+ for (int i = numBackgrounds - 1; i >= 0; i--) {
203+ model.setTranslation(glm::vec3(4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0)));
204+ batch.setModelTransform(model);
205+ if (skyboxStack[i]) {
206+ batch.setResourceTexture(ZONE_SKYBOX_MAP, skyboxStack[i]->getCubemap());
207+ batch.setUniformBuffer(ZONE_SKYBOX_BUFFER, skyboxStack[i]->getSchemaBuffer());
208+ batch.draw(gpu::TRIANGLE_STRIP, 4);
209+ }
210+ }
211+ });
212+}
libraries/render-utils/src/ZoneRenderer.h+40−1
@@ -14,12 +14,15 @@ · 13 unmodified lines
1414
1515 #include "render/Engine.h"
1616
17+#include "DeferredFrameTransform.h"
18+
1719 class ZoneRendererConfig : public render::Task::Config {
1820 Q_OBJECT
1921 Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty)
2022 public:
2123
22− ZoneRendererConfig() : render::Task::Config(false) {}
24+ ZoneRendererConfig() : render::Task::Config(
25+ ) {}
2326
2427 int maxDrawn { -1 };
2528
@@ -49,4 +52,40 @@ protected: · 23 unmodified lines
4952 int _maxDrawn; // initialized by Config
5053 };
5154
55+class DebugZoneLighting {
56+public:
57+ class Config : public render::JobConfig {
58+ public:
59+ Config(bool enabled = false) : JobConfig(enabled) {}
60+ };
61+
62+ using Inputs = DeferredFrameTransformPointer;
63+ using JobModel = render::Job::ModelI<DebugZoneLighting, Inputs, Config>;
64+
65+ DebugZoneLighting() {}
66+
67+ void configure(const Config& configuration) {}
68+ void run(const render::RenderContextPointer& context, const Inputs& inputs);
69+
70+protected:
71+
72+ enum Slots {
73+ ZONE_DEFERRED_TRANSFORM_BUFFER = 0,
74+ ZONE_KEYLIGHT_BUFFER,
75+ ZONE_AMBIENT_BUFFER,
76+ ZONE_AMBIENT_MAP,
77+ ZONE_SKYBOX_BUFFER,
78+ ZONE_SKYBOX_MAP,
79+ };
80+
81+ gpu::PipelinePointer _keyLightPipeline;
82+ gpu::PipelinePointer _ambientPipeline;
83+ gpu::PipelinePointer _backgroundPipeline;
84+
85+ const gpu::PipelinePointer& getKeyLightPipeline();
86+ const gpu::PipelinePointer& getAmbientPipeline();
87+ const gpu::PipelinePointer& getBackgroundPipeline();
88+};
89+
90+
5291 #endif
libraries/render-utils/src/directional_light.slf+0−47
@@ -1,47 +0,0 @@
1−<@include gpu/Config.slh@>
2−<$VERSION_HEADER$>
3−// Generated on <$_SCRIBE_DATE$>
4−//
5−// directional_light.frag
6−// fragment shader
7−//
8−// Created by Andrzej Kapolka on 9/3/14.
9−// Copyright 2016 High Fidelity, Inc.
10−//
11−// Distributed under the Apache License, Version 2.0.
12−// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
13−//
14−
15−<@include DeferredBufferRead.slh@>
16−<@include DeferredGlobalLight.slh@>
17−
18−<$declareEvalLightmappedColor()$>
19−<$declareEvalAmbientGlobalColor()$>
20−
21−in vec2 _texCoord0;
22−out vec4 _fragColor;
23−
24−void main(void) {
25− DeferredFrameTransform deferredTransform = getDeferredFrameTransform();
26− DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0);
27−
28− float shadowAttenuation = 1.0;
29−
30− if (frag.mode == FRAG_MODE_UNLIT) {
31− discard;
32− } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) {
33− discard;
34− } else {
35− vec3 color = evalAmbientGlobalColor(
36− getViewInverse(),
37− shadowAttenuation,
38− frag.obscurance,
39− frag.position.xyz,
40− frag.normal,
41− frag.albedo,
42− frag.fresnel,
43− frag.metallic,
44− frag.roughness);
45− _fragColor = vec4(color, 1.0);
46− }
47−}
libraries/render-utils/src/directional_light_shadow.slf+0−49
@@ -1,49 +0,0 @@
1−<@include gpu/Config.slh@>
2−<$VERSION_HEADER$>
3−// Generated on <$_SCRIBE_DATE$>
4−//
5−// directional_light_shadow.frag
6−// fragment shader
7−//
8−// Created by Zach Pomerantz on 1/18/2016.
9−// Copyright 2016 High Fidelity, Inc.
10−//
11−// Distributed under the Apache License, Version 2.0.
12−// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
13−//
14−
15−<@include Shadow.slh@>
16−<@include DeferredBufferRead.slh@>
17−<@include DeferredGlobalLight.slh@>
18−
19−<$declareEvalLightmappedColor()$>
20−<$declareEvalAmbientGlobalColor()$>
21−
22−in vec2 _texCoord0;
23−out vec4 _fragColor;
24−
25−void main(void) {
26− DeferredFrameTransform deferredTransform = getDeferredFrameTransform();
27− DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0);
28−
29− vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0);
30− float shadowAttenuation = evalShadowAttenuation(worldPos);
31−
32− if (frag.mode == FRAG_MODE_UNLIT) {
33− discard;
34− } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) {
35− discard;
36− } else {
37− vec3 color = evalAmbientGlobalColor(
38− getViewInverse(),
39− shadowAttenuation,
40− frag.obscurance,
41− frag.position.xyz,
42− frag.normal,
43− frag.albedo,
44− frag.fresnel,
45− frag.metallic,
46− frag.roughness);
47− _fragColor = vec4(color, 1.0);
48− }
49−}
libraries/render-utils/src/hit_effect.slf+0−27
@@ -1,27 +0,0 @@
1−<@include gpu/Config.slh@>
2−<$VERSION_HEADER$>
3−// Generated on <$_SCRIBE_DATE$>
4−//
5−
6−// hit_effect.frag
7−// fragment shader
8−//
9−// Created by Eric Levin on 7/20
10−// Copyright 2015 High Fidelity, Inc.
11−//
12−// Distributed under the Apache License, Version 2.0.
13−// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
14−//
15−
16−
17−<@include DeferredBufferWrite.slh@>
18−
19−in vec2 varQuadPosition;
20−out vec4 outFragColor;
21−
22−void main(void) {
23− vec2 center = vec2(0.0, 0.0);
24− float distFromCenter = distance( vec2(0.0, 0.0), varQuadPosition);
25− float alpha = mix(0.0, 0.5, pow(distFromCenter,5.));
26− outFragColor = vec4(1.0, 0.0, 0.0, alpha);
27−}
libraries/render-utils/src/point_light.slf+0−85
@@ -1,85 +0,0 @@
1−<@include gpu/Config.slh@>
2−<$VERSION_HEADER$>
3−// Generated on <$_SCRIBE_DATE$>
4−//
5−// point_light.frag
6−// fragment shader
7−//
8−// Created by Sam Gateau on 9/18/15.
9−// Copyright 2014 High Fidelity, Inc.
10−//
11−// Distributed under the Apache License, Version 2.0.
12−// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
13−//
14−<!
15−// Everything about deferred buffer
16−<@include DeferredBufferRead.slh@>
17−
18−<$declareDeferredCurvature()$>
19−
20−// Everything about light
21−<@include model/Light.slh@>
22−<$declareLightBuffer()$>
23−
24−<@include LightingModel.slh@>
25−
26−<@include LightPoint.slh@>
27−<$declareLightingPoint(supportScattering)$>
28−
29−
30−uniform vec4 texcoordFrameTransform;
31−
32−in vec4 _texCoord0;!>
33−out vec4 _fragColor;
34−
35−void main(void) {
36− _fragColor = vec4(1.0, 1.0, 1.0, 1.0);
37−
38− <!
39− DeferredFrameTransform deferredTransform = getDeferredFrameTransform();
40−
41− // Grab the fragment data from the uv
42− vec2 texCoord = _texCoord0.st / _texCoord0.q;
43− texCoord *= texcoordFrameTransform.zw;
44− texCoord += texcoordFrameTransform.xy;
45−
46− DeferredFragment frag = unpackDeferredFragment(deferredTransform, texCoord);
47−
48− if (frag.mode == FRAG_MODE_UNLIT) {
49− discard;
50− }
51−
52− // Need the light now
53− Light light = getLight();
54−
55− // Frag pos in world
56− mat4 invViewMat = getViewInverse();
57− vec4 fragPos = invViewMat * frag.position;
58−
59− // Clip againgst the light volume and Make the Light vector going from fragment to light center in world space
60− vec4 fragLightVecLen2;
61− if (!lightVolume_clipFragToLightVolumePoint(light.volume, fragPos.xyz, fragLightVecLen2)) {
62− discard;
63− }
64−
65− // Frag to eye vec
66− vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0);
67− vec3 fragEyeDir = normalize(fragEyeVector.xyz);
68−
69−
70− vec3 diffuse;
71− vec3 specular;
72− vec4 midNormalCurvature;
73− vec4 lowNormalCurvature;
74− if (frag.mode == FRAG_MODE_SCATTERING) {
75− unpackMidLowNormalCurvature(texCoord, midNormalCurvature, lowNormalCurvature);
76− }
77− evalLightingPoint(diffuse, specular, light,
78− fragLightVecLen2.xyz, fragEyeDir, frag.normal, frag.roughness,
79− frag.metallic, frag.fresnel, frag.albedo, 1.0,
80− frag.scattering, midNormalCurvature, lowNormalCurvature);
81−
82− _fragColor.rgb += diffuse;
83− _fragColor.rgb += specular;
84− !>
85−}
libraries/render-utils/src/spot_light.slf+0−115
@@ -1,115 +0,0 @@
1−<@include gpu/Config.slh@>
2−<$VERSION_HEADER$>
3−// Generated on <$_SCRIBE_DATE$>
4−//
5−// spot_light.frag
6−// fragment shader
7−//
8−// Created by Sam Gateau on 9/18/15.
9−// Copyright 2014 High Fidelity, Inc.
10−//
11−// Distributed under the Apache License, Version 2.0.
12−// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
13−//
14−
15−// Everything about deferred buffer
16−<!<@include DeferredBufferRead.slh@>
17−
18−<$declareDeferredCurvature()$>
19−
20−// Everything about light
21−<@include model/Light.slh@>
22−<$declareLightBuffer(256)$>
23−uniform lightIndexBuffer {
24− int lightIndex[256];
25−};
26−<@include LightingModel.slh@>
27−
28−<@include LightPoint.slh@>
29−<$declareLightingPoint(supportScattering)$>
30−<@include LightSpot.slh@>
31−<$declareLightingSpot(supportScattering)$>
32−
33−//uniform vec4 texcoordFrameTransform;
34−!>
35−
36−
37−//in vec4 _texCoord0;
38−//flat in int instanceID;
39−out vec4 _fragColor;
40−
41−void main(void) {
42− _fragColor = vec4(1.0, 1.0, 1.0, 1.0);
43−
44−// DeferredFrameTransform deferredTransform = getDeferredFrameTransform();
45−
46− // Grab the fragment data from the uv
47− //vec2 texCoord = _texCoord0.st;/* / _texCoord0.q;
48− /*texCoord *= texcoordFrameTransform.zw;
49− texCoord += texcoordFrameTransform.xy;*/
50− /*
51− vec4 fragPosition = unpackDeferredPositionFromZeye(texCoord);
52− DeferredFragment frag = unpackDeferredFragmentNoPosition(texCoord);
53−
54− if (frag.mode == FRAG_MODE_UNLIT) {
55− discard;
56− }
57−
58−
59− // frag.depthVal = depthValue;
60− frag.position = fragPosition;
61−
62− vec4 midNormalCurvature;
63− vec4 lowNormalCurvature;
64− if (frag.mode == FRAG_MODE_SCATTERING) {
65− unpackMidLowNormalCurvature(texCoord, midNormalCurvature, lowNormalCurvature);
66− }
67−
68− // Frag pos in world
69− mat4 invViewMat = getViewInverse();
70− vec4 fragPos = invViewMat * fragPosition;
71−
72− // Frag to eye vec
73− vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0);
74− vec3 fragEyeDir = normalize(fragEyeVector.xyz);
75−
76− int numLights = lightIndex[0];
77− for (int i = 0; i < numLights; i++) {
78− // Need the light now
79− Light light = getLight(lightIndex[i + 1]);
80− bool isSpot = light_isSpot(light);
81− // Clip againgst the light volume and Make the Light vector going from fragment to light center in world space
82− vec4 fragLightVecLen2;
83− vec4 fragLightDirLen;
84− float cosSpotAngle;
85− if (isSpot) {
86− if (!clipFragToLightVolumeSpot(light, fragPos.xyz, fragLightVecLen2, fragLightDirLen, cosSpotAngle)) {
87− continue;
88− }
89− } else {
90− if (!clipFragToLightVolumePoint(light, fragPos.xyz, fragLightVecLen2)) {
91− continue;
92− }
93− }
94−
95− vec3 diffuse;
96− vec3 specular;
97−
98− if (isSpot) {
99− evalLightingSpot(diffuse, specular, light,
100− fragLightDirLen.xyzw, cosSpotAngle, fragEyeDir, frag.normal, frag.roughness,
101− frag.metallic, frag.fresnel, frag.albedo, 1.0,
102− frag.scattering, midNormalCurvature, lowNormalCurvature);
103− } else {
104− evalLightingPoint(diffuse, specular, light,
105− fragLightVecLen2.xyz, fragEyeDir, frag.normal, frag.roughness,
106− frag.metallic, frag.fresnel, frag.albedo, 1.0,
107− frag.scattering, midNormalCurvature, lowNormalCurvature);
108− }
109−
110− _fragColor.rgb += diffuse;
111− _fragColor.rgb += specular;
112− }
113− */
114−}
115−
libraries/render-utils/src/stencil_drawMask.slf+23−0
@@ -0,0 +1,23 @@
1+<@include gpu/Config.slh@>
2+<$VERSION_HEADER$>
3+// Generated on <$_SCRIBE_DATE$>
4+//
5+// stencil_drawMask.slf
6+// fragment shader
7+//
8+// Created by Sam Gateau on 5/31/17.
9+// Copyright 2017 High Fidelity, Inc.
10+//
11+// Distributed under the Apache License, Version 2.0.
12+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
13+//
14+
15+in vec2 varTexCoord0;
16+
17+float aspectRatio = 0.95;
18+
19+void main(void) {
20+ vec2 pos = varTexCoord0 * 2.0 - vec2(1.0);
21+ pos.x = aspectRatio * (pos.x * (pos.x > 0.0 ? 2.0 : -2.0) - 1.0);
22+ if (1.0 - dot(pos.xy, pos.xy) > 0.0 ) discard;
23+}
libraries/render-utils/src/text/Font.cpp+3−0
@@ -14,6 +14,8 @@ · 13 unmodified lines
1414 #include "../RenderUtilsLogging.h"
1515 #include "FontFamilies.h"
1616
17+static std::mutex fontMutex;
18+
1719 struct TextureVertex {
1820 glm::vec2 pos;
1921 glm::vec2 tex;
@@ -56,6 +58,7 @@ Font::Pointer Font::load(QIODevice& fontFile) { · 36 unmodified lines
5658 }
5759
5860 Font::Pointer Font::load(const QString& family) {
61+ std::lock_guard<std::mutex> lock(fontMutex);
5962 if (!LOADED_FONTS.contains(family)) {
6063
6164 static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" };
libraries/render-utils/src/text/Font.h+1−1
@@ -31,10 +31,10 @@ public: · 30 unmodified lines
3131 const glm::vec4* color, EffectType effectType,
3232 const glm::vec2& bound, bool layered = false);
3333
34− static Pointer load(QIODevice& fontFile);
3534 static Pointer load(const QString& family);
3635
3736 private:
37+ static Pointer load(QIODevice& fontFile);
3838 QStringList tokenizeForWrapping(const QString& str) const;
3939 QStringList splitLines(const QString& str) const;
4040 glm::vec2 computeTokenExtent(const QString& str) const;
libraries/render-utils/src/zone_draw.slh+37−0
@@ -0,0 +1,37 @@
1+
2+// Generated on <$_SCRIBE_DATE$>
3+//
4+// Created by Sam Gateau on 5/17/17.
5+// Copyright 2017 High Fidelity, Inc.
6+//
7+// Distributed under the Apache License, Version 2.0.
8+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
9+//
10+
11+<@include DeferredTransform.slh@>
12+<$declareDeferredFrameTransform()$>
13+
14+<@func evalGlobeWidget()@>
15+ const float SCOPE_RADIUS = 1.0;
16+ const float SCOPE_RADIUS2 = SCOPE_RADIUS * SCOPE_RADIUS;
17+ const float EDGE_HALFWIDTH = 0.025;
18+ const float EDGE_HALFWIDTH2 = EDGE_HALFWIDTH * EDGE_HALFWIDTH;
19+ const float OUT_RADIUS = SCOPE_RADIUS + EDGE_HALFWIDTH;
20+
21+ vec2 sphereUV = (varTexCoord0.xy * 2.0 - vec2(1.0)) * OUT_RADIUS;
22+ float sphereR2 = dot(sphereUV.xy, sphereUV.xy);
23+ if (sphereR2 > OUT_RADIUS * OUT_RADIUS) {
24+ discard;
25+ }
26+ float sphereR = sqrt(sphereR2);
27+
28+ float edgeFalloff = (SCOPE_RADIUS - sphereR) / (EDGE_HALFWIDTH);
29+ float edgeFalloff2 = min(1.0, edgeFalloff * edgeFalloff);
30+
31+ vec4 base = vec4(0.0, 0.0, 0.0, 1.0 - edgeFalloff2);
32+ if (sphereR2 > SCOPE_RADIUS2) {
33+ _fragColor = base;
34+ return;
35+ }
36+<@endfunc@>
37+
libraries/render-utils/src/zone_drawAmbient.slf+54−0
@@ -0,0 +1,54 @@
1+<@include gpu/Config.slh@>
2+<$VERSION_HEADER$>
3+// Generated on <$_SCRIBE_DATE$>
4+//
5+// Created by Sam Gateau on 5/16/17.
6+// Copyright 2017 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+<@include zone_draw.slh@>
12+
13+<@include model/Light.slh@>
14+
15+<@include LightingModel.slh@>
16+<$declareLightAmbientBuffer()$>
17+
18+<@include LightAmbient.slh@>
19+
20+<$declareLightingAmbient(_SCRIBE_NULL, 1, _SCRIBE_NULL, _SCRIBE_NULL)$>
21+
22+in vec2 varTexCoord0;
23+out vec4 _fragColor;
24+
25+void main(void) {
26+
27+ <$evalGlobeWidget()$>
28+
29+ vec3 spherePos = normalize(vec3(sphereUV, sqrt(1.0 - sphereR2)));
30+
31+
32+ vec3 fragNormal = vec3(getViewInverse() * vec4(spherePos, 0.0));
33+
34+
35+ LightAmbient lightAmbient = getLightAmbient();
36+
37+
38+ float roughness = 0.1;
39+ float levels = getLightAmbientMapNumMips(lightAmbient);
40+ float lod = min(((roughness)* levels), levels);
41+ vec3 ambientMap = evalSkyboxLight(fragNormal, lod).xyz;
42+ vec3 ambientSH = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(lightAmbient), fragNormal).xyz;
43+
44+ // vec3 ambient = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(lightAmbient), fragNormal).xyz;
45+ // _fragColor = vec4( 0.5 * (fragNormal + vec3(1.0)), 1.0);
46+
47+ vec3 color = (sphereUV.x > 0 ? ambientMap : ambientSH);
48+
49+ color = color * 1.0 - base.w + base.xyz * base.w;
50+ const float INV_GAMMA_22 = 1.0 / 2.2;
51+ _fragColor = vec4(pow(color, vec3(INV_GAMMA_22)), 1.0);
52+}
53+
54+
libraries/render-utils/src/zone_drawKeyLight.slf+57−0
@@ -0,0 +1,57 @@
1+<@include gpu/Config.slh@>
2+<$VERSION_HEADER$>
3+// Generated on <$_SCRIBE_DATE$>
4+//
5+// Created by Sam Gateau on 5/16/17.
6+// Copyright 2017 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+<@include zone_draw.slh@>
12+
13+<@include model/Light.slh@>
14+
15+<@include LightingModel.slh@>
16+<$declareLightBuffer()$>
17+
18+<@include LightDirectional.slh@>
19+<$declareLightingDirectional(_SCRIBE_NULL)$>
20+
21+in vec2 varTexCoord0;
22+out vec4 _fragColor;
23+
24+void main(void) {
25+
26+ <$evalGlobeWidget()$>
27+
28+ Light light = getLight();
29+ vec3 lightDirection = normalize(getLightDirection(light));
30+ vec3 lightIrradiance = getLightIrradiance(light);
31+ vec3 color = vec3(0.0);
32+
33+ const float INOUT_RATIO = 0.4;
34+ const float SUN_THRESHOLD = 0.99;
35+
36+ vec3 outSpherePos = normalize(vec3(sphereUV, -sqrt(1.0 - sphereR2)));
37+ vec3 outNormal = vec3(getViewInverse() * vec4(outSpherePos, 0.0));
38+
39+ float val = step(SUN_THRESHOLD, dot(-lightDirection, outNormal));
40+
41+ color = lightIrradiance * vec3(val);
42+
43+ if (sphereR2 < INOUT_RATIO * INOUT_RATIO * SCOPE_RADIUS2) {
44+ vec2 inSphereUV = sphereUV / INOUT_RATIO;
45+ vec3 inSpherePos = normalize(vec3(inSphereUV, sqrt(1.0 - dot(inSphereUV.xy, inSphereUV.xy))));
46+ vec3 inNormal = vec3(getViewInverse() * vec4(inSpherePos, 0.0));
47+
48+ vec3 marbleColor = max(lightIrradiance * vec3(dot(-lightDirection, inNormal)), vec3(0.01));
49+ color += marbleColor;
50+ }
51+
52+ color = color * 1.0 - base.w + base.xyz * base.w;
53+ const float INV_GAMMA_22 = 1.0 / 2.2;
54+ _fragColor = vec4(pow(color, vec3(INV_GAMMA_22)), 1.0);
55+}
56+
57+
libraries/render-utils/src/zone_drawSkybox.slf+48−0
@@ -0,0 +1,48 @@
1+<@include gpu/Config.slh@>
2+<$VERSION_HEADER$>
3+// Generated on <$_SCRIBE_DATE$>
4+//
5+// Created by Sam Gateau on 5/16/17.
6+// Copyright 2017 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+<@include zone_draw.slh@>
12+
13+uniform samplerCube skyboxMap;
14+
15+struct Skybox {
16+ vec4 color;
17+};
18+
19+uniform skyboxBuffer {
20+ Skybox skybox;
21+};
22+
23+in vec2 varTexCoord0;
24+out vec4 _fragColor;
25+
26+void main(void) {
27+ <$evalGlobeWidget()$>
28+
29+ vec3 spherePos = normalize(vec3(sphereUV, -sqrt(1.0 - sphereR2)));
30+
31+ vec3 direction = vec3(getViewInverse() * vec4(spherePos, 0.0));
32+
33+ vec3 color = skybox.color.rgb;
34+
35+ // blend is only set if there is a cubemap
36+ if (skybox.color.a > 0.0) {
37+ color = texture(skyboxMap, direction).rgb;
38+ if (skybox.color.a < 1.0) {
39+ color *= skybox.color.rgb;
40+ }
41+ }
42+
43+ color = color * 1.0 - base.w + base.xyz * base.w;
44+ const float INV_GAMMA_22 = 1.0 / 2.2;
45+ _fragColor = vec4(pow(color, vec3(INV_GAMMA_22)), 1.0);
46+}
47+
48+
libraries/render/src/render/EngineStats.cpp+0−2
@@ -63,6 +63,4 @@ void EngineStats::run(const RenderContextPointer& renderContext) { · 62 unmodified lines
6363
6464 config->frameSetPipelineCount = _gpuStats._PSNumSetPipelines;
6565 config->frameSetInputFormatCount = _gpuStats._ISNumFormatChanges;
66−
67− config->emitDirty();
6866 }
libraries/render/src/task/Config.cpp+2−0
@@ -34,6 +34,7 @@ void TaskConfig::connectChildConfig(QConfigPointer childConfig, const std::strin · 33 unmodified lines
3434 if (childConfig->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) {
3535 // Connect dirty->refresh if defined
3636 QObject::connect(childConfig.get(), SIGNAL(dirty()), this, SLOT(refresh()));
37+ QObject::connect(childConfig.get(), SIGNAL(dirtyEnabled()), this, SLOT(refresh()));
3738 }
3839 }
3940
@@ -50,6 +51,7 @@ void TaskConfig::transferChildrenConfigs(QConfigPointer source) { · 10 unmodified lines
5051 if (child->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) {
5152 // Connect dirty->refresh if defined
5253 QObject::connect(child, SIGNAL(dirty()), this, SLOT(refresh()));
54+ QObject::connect(child, SIGNAL(dirtyEnabled()), this, SLOT(refresh()));
5355 }
5456 }
5557 }
libraries/render/src/task/Config.h+3−2
@@ -89,7 +89,7 @@ protected: · 88 unmodified lines
8989 class JobConfig : public QObject {
9090 Q_OBJECT
9191 Q_PROPERTY(double cpuRunTime READ getCPURunTime NOTIFY newStats()) //ms
92− Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
92+ Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY dirtyEnabled())
9393
9494 double _msCPURunTime{ 0.0 };
9595 public:
@@ -99,7 +99,7 @@ public: · 3 unmodified lines
9999 JobConfig(bool enabled) : alwaysEnabled{ false }, enabled{ enabled } {}
100100
101101 bool isEnabled() { return alwaysEnabled || enabled; }
102− void setEnabled(bool enable) { enabled = alwaysEnabled || enable; }
102+ void setEnabled(bool enable) { enabled = alwaysEnabled || enable; emit dirtyEnabled(); }
103103
104104 bool alwaysEnabled{ true };
105105 bool enabled{ true };
@@ -121,6 +121,7 @@ public slots: · 15 unmodified lines
121121 signals:
122122 void loaded();
123123 void newStats();
124+ void dirtyEnabled();
124125 };
125126
126127 class TaskConfig : public JobConfig {
libraries/render/src/task/Task.h+1−0
@@ -170,6 +170,7 @@ protected: · 169 unmodified lines
170170 std::string _name = "";
171171 };
172172
173+
173174 // A task is a specialized job to run a collection of other jobs
174175 // It can be created on any type T by aliasing the type JobModel in the class T
175176 // using JobModel = Task::Model<T>
libraries/script-engine/src/TabletScriptingInterface.cpp+27−19
@@ -542,7 +542,7 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS · 541 unmodified lines
542542
543543 QObject* TabletProxy::addButton(const QVariant& properties) {
544544 auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap()));
545− std::lock_guard<std::mutex> guard(_tabletMutex);
545+ std::unique_lock<std::mutex> guard(_tabletMutex);
546546 _tabletButtonProxies.push_back(tabletButtonProxy);
547547 if (!_toolbarMode && _qmlTabletRoot) {
548548 auto tablet = getQmlTablet();
@@ -552,7 +552,6 @@ QObject* TabletProxy::addButton(const QVariant& properties) { · 3 unmodified lines
552552 qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
553553 }
554554 } else if (_toolbarMode) {
555−
556555 auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
557556 QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
558557
@@ -561,6 +560,8 @@ QObject* TabletProxy::addButton(const QVariant& properties) { · 2 unmodified lines
561560 connectionType = Qt::BlockingQueuedConnection;
562561 }
563562
563+ guard.unlock();
564+
564565 // copy properties from tablet button proxy to toolbar button proxy.
565566 QObject* toolbarButtonProxy = nullptr;
566567 bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties()));
@@ -578,31 +579,38 @@ bool TabletProxy::onHomeScreen() { · 11 unmodified lines
578579 }
579580
580581 void TabletProxy::removeButton(QObject* tabletButtonProxy) {
581− std::lock_guard<std::mutex> guard(_tabletMutex);
582+ std::unique_lock<std::mutex> guard(_tabletMutex);
582583
583584 auto tablet = getQmlTablet();
584585 if (!tablet) {
585586 qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
586587 }
587588
588− auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy);
589− if (iter != _tabletButtonProxies.end()) {
590− if (!_toolbarMode && _qmlTabletRoot) {
591− (*iter)->setQmlButton(nullptr);
592− if (tablet) {
593− QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getProperties()));
594− }
595− } else if (_toolbarMode) {
596− auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
597− QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
598−
599− // remove button from toolbarProxy
600− QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getUuid().toString()));
601− (*iter)->setToolbarButtonProxy(nullptr);
589+ QSharedPointer<TabletButtonProxy> buttonProxy;
590+ {
591+ auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy);
592+ if (iter == _tabletButtonProxies.end()) {
593+ qCWarning(scriptengine) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy;
594+ return;
602595 }
596+ buttonProxy = *iter;
603597 _tabletButtonProxies.erase(iter);
604− } else {
605− qCWarning(scriptengine) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy;
598+ }
599+
600+ if (!_toolbarMode && _qmlTabletRoot) {
601+ buttonProxy->setQmlButton(nullptr);
602+ if (tablet) {
603+ guard.unlock();
604+ QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties()));
605+ }
606+ } else if (_toolbarMode) {
607+ auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
608+ QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
609+
610+ // remove button from toolbarProxy
611+ guard.unlock();
612+ QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString()));
613+ buttonProxy->setToolbarButtonProxy(nullptr);
606614 }
607615 }
608616
libraries/shared/src/CubicHermiteSpline.h+113−0
@@ -0,0 +1,113 @@
1+//
2+// CubicHermiteSpline.h
3+//
4+// Copyright 2017 High Fidelity, Inc.
5+//
6+// Distributed under the Apache License, Version 2.0.
7+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
8+//
9+
10+#ifndef hifi_CubicHermiteSpline_h
11+#define hifi_CubicHermiteSpline_h
12+
13+#include "GLMHelpers.h"
14+
15+class CubicHermiteSplineFunctor {
16+public:
17+ CubicHermiteSplineFunctor() : _p0(), _m0(), _p1(), _m1() {}
18+ CubicHermiteSplineFunctor(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : _p0(p0), _m0(m0), _p1(p1), _m1(m1) {}
19+
20+ CubicHermiteSplineFunctor(const CubicHermiteSplineFunctor& orig) : _p0(orig._p0), _m0(orig._m0), _p1(orig._p1), _m1(orig._m1) {}
21+
22+ virtual ~CubicHermiteSplineFunctor() {}
23+
24+ // evalute the hermite curve at parameter t (0..1)
25+ glm::vec3 operator()(float t) const {
26+ float t2 = t * t;
27+ float t3 = t2 * t;
28+ float w0 = 2.0f * t3 - 3.0f * t2 + 1.0f;
29+ float w1 = t3 - 2.0f * t2 + t;
30+ float w2 = -2.0f * t3 + 3.0f * t2;
31+ float w3 = t3 - t2;
32+ return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
33+ }
34+
35+ // evaulate the first derivative of the hermite curve at parameter t (0..1)
36+ glm::vec3 d(float t) const {
37+ float t2 = t * t;
38+ float w0 = -6.0f * t + 6.0f * t2;
39+ float w1 = 1.0f - 4.0f * t + 3.0f * t2;
40+ float w2 = 6.0f * t - 6.0f * t2;
41+ float w3 = -2.0f * t + 3.0f * t2;
42+ return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
43+ }
44+
45+ // evaulate the second derivative of the hermite curve at paramter t (0..1)
46+ glm::vec3 d2(float t) const {
47+ float w0 = -6.0f + 12.0f * t;
48+ float w1 = -4.0f + 6.0f * t;
49+ float w2 = 6.0f - 12.0f * t;
50+ float w3 = -2.0f + 6.0f * t;
51+ return w0 + _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
52+ }
53+
54+protected:
55+ glm::vec3 _p0;
56+ glm::vec3 _m0;
57+ glm::vec3 _p1;
58+ glm::vec3 _m1;
59+};
60+
61+class CubicHermiteSplineFunctorWithArcLength : public CubicHermiteSplineFunctor {
62+public:
63+ enum Constants { NUM_SUBDIVISIONS = 30 };
64+
65+ CubicHermiteSplineFunctorWithArcLength() : CubicHermiteSplineFunctor() {
66+ memset(_values, 0, sizeof(float) * (NUM_SUBDIVISIONS + 1));
67+ }
68+ CubicHermiteSplineFunctorWithArcLength(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : CubicHermiteSplineFunctor(p0, m0, p1, m1) {
69+ // initialize _values with the accumulated arcLength along the spline.
70+ const float DELTA = 1.0f / NUM_SUBDIVISIONS;
71+ float alpha = 0.0f;
72+ float accum = 0.0f;
73+ _values[0] = 0.0f;
74+ for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) {
75+ accum += glm::distance(this->operator()(alpha),
76+ this->operator()(alpha + DELTA));
77+ alpha += DELTA;
78+ _values[i] = accum;
79+ }
80+ }
81+
82+ CubicHermiteSplineFunctorWithArcLength(const CubicHermiteSplineFunctorWithArcLength& orig) : CubicHermiteSplineFunctor(orig) {
83+ memcpy(_values, orig._values, sizeof(float) * (NUM_SUBDIVISIONS + 1));
84+ }
85+
86+ // given the spline parameter (0..1) output the arcLength of the spline up to that point.
87+ float arcLength(float t) const {
88+ float index = t * NUM_SUBDIVISIONS;
89+ int prevIndex = std::min(std::max(0, (int)glm::floor(index)), (int)NUM_SUBDIVISIONS);
90+ int nextIndex = std::min(std::max(0, (int)glm::ceil(index)), (int)NUM_SUBDIVISIONS);
91+ float alpha = glm::fract(index);
92+ return lerp(_values[prevIndex], _values[nextIndex], alpha);
93+ }
94+
95+ // given an arcLength compute the spline parameter (0..1) that cooresponds to that arcLength.
96+ float arcLengthInverse(float s) const {
97+ // find first item in _values that is > s.
98+ int nextIndex;
99+ for (nextIndex = 0; nextIndex < NUM_SUBDIVISIONS; nextIndex++) {
100+ if (_values[nextIndex] > s) {
101+ break;
102+ }
103+ }
104+ int prevIndex = std::min(std::max(0, nextIndex - 1), (int)NUM_SUBDIVISIONS);
105+ float alpha = glm::clamp((s - _values[prevIndex]) / (_values[nextIndex] - _values[prevIndex]), 0.0f, 1.0f);
106+ const float DELTA = 1.0f / NUM_SUBDIVISIONS;
107+ return lerp(prevIndex * DELTA, nextIndex * DELTA, alpha);
108+ }
109+protected:
110+ float _values[NUM_SUBDIVISIONS + 1];
111+};
112+
113+#endif // hifi_CubicHermiteSpline_h
libraries/shared/src/Extents.h+1−1
@@ -69,7 +69,7 @@ public: · 68 unmodified lines
6969 glm::vec3 size() const { return maximum - minimum; }
7070 float largestDimension() const { return glm::compMax(size()); }
7171
72− /// \return new Extents which is original rotated around orign by rotation
72+ /// \return new Extents which is original rotated around origin by rotation
7373 Extents getRotated(const glm::quat& rotation) const {
7474 Extents temp(minimum, maximum);
7575 temp.rotate(rotation);
libraries/shared/src/GLMHelpers.cpp+24−25
@@ -49,7 +49,7 @@ const mat4 Matrices::Z_180 { createMatFromQuatAndPos(Quaternions::Z_180, Vectors · 48 unmodified lines
4949 glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float proportion) {
5050 float cosa = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w;
5151 float ox = q2.x, oy = q2.y, oz = q2.z, ow = q2.w, s0, s1;
52−
52+
5353 // adjust signs if necessary
5454 if (cosa < 0.0f) {
5555 cosa = -cosa;
@@ -58,19 +58,19 @@ glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float proportion) { · 2 unmodified lines
5858 oz = -oz;
5959 ow = -ow;
6060 }
61−
61+
6262 // calculate coefficients; if the angle is too close to zero, we must fall back
6363 // to linear interpolation
6464 if ((1.0f - cosa) > EPSILON) {
6565 float angle = acosf(cosa), sina = sinf(angle);
6666 s0 = sinf((1.0f - proportion) * angle) / sina;
6767 s1 = sinf(proportion * angle) / sina;
68−
68+
6969 } else {
7070 s0 = 1.0f - proportion;
7171 s1 = proportion;
7272 }
73−
73+
7474 return glm::normalize(glm::quat(s0 * q1.w + s1 * ow, s0 * q1.x + s1 * ox, s0 * q1.y + s1 * oy, s0 * q1.z + s1 * oz));
7575 }
7676
@@ -105,10 +105,10 @@ int unpackFloatVec3FromSignedTwoByteFixed(const unsigned char* sourceBuffer, glm · 28 unmodified lines
105105
106106 int packFloatAngleToTwoByte(unsigned char* buffer, float degrees) {
107107 const float ANGLE_CONVERSION_RATIO = (std::numeric_limits<uint16_t>::max() / 360.0f);
108−
108+
109109 uint16_t angleHolder = floorf((degrees + 180.0f) * ANGLE_CONVERSION_RATIO);
110110 memcpy(buffer, &angleHolder, sizeof(uint16_t));
111−
111+
112112 return sizeof(uint16_t);
113113 }
114114
@@ -125,7 +125,7 @@ int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput · 10 unmodified lines
125125 quatParts[1] = floorf((quatNormalized.y + 1.0f) * QUAT_PART_CONVERSION_RATIO);
126126 quatParts[2] = floorf((quatNormalized.z + 1.0f) * QUAT_PART_CONVERSION_RATIO);
127127 quatParts[3] = floorf((quatNormalized.w + 1.0f) * QUAT_PART_CONVERSION_RATIO);
128−
128+
129129 memcpy(buffer, &quatParts, sizeof(quatParts));
130130 return sizeof(quatParts);
131131 }
@@ -133,12 +133,12 @@ int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput · 1 unmodified line
133133 int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatOutput) {
134134 uint16_t quatParts[4];
135135 memcpy(&quatParts, buffer, sizeof(quatParts));
136−
136+
137137 quatOutput.x = ((quatParts[0] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f;
138138 quatOutput.y = ((quatParts[1] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f;
139139 quatOutput.z = ((quatParts[2] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f;
140140 quatOutput.w = ((quatParts[3] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f;
141−
141+
142142 return sizeof(quatParts);
143143 }
144144
@@ -235,7 +235,7 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { · 90 unmodified lines
235235 atan2f(q.y * q.z + q.x * q.w, 0.5f - (q.x * q.x + q.y * q.y)),
236236 asinf(sy),
237237 atan2f(q.x * q.y + q.z * q.w, 0.5f - (q.y * q.y + q.z * q.z)));
238−
238+
239239 } else {
240240 // not a unique solution; x + z = atan2(-m21, m11)
241241 eulers = glm::vec3(
@@ -250,7 +250,7 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { · 8 unmodified lines
250250 PI_OVER_TWO,
251251 -atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z)));
252252 }
253−
253+
254254 // adjust so that z, rather than y, is in [-pi/2, pi/2]
255255 if (eulers.z < -PI_OVER_TWO) {
256256 if (eulers.x < 0.0f) {
@@ -265,7 +265,7 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { · 8 unmodified lines
265265 eulers.y -= PI;
266266 }
267267 eulers.z += PI;
268−
268+
269269 } else if (eulers.z > PI_OVER_TWO) {
270270 if (eulers.x < 0.0f) {
271271 eulers.x += PI;
@@ -320,7 +320,7 @@ glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { · 48 unmodified lines
320320 for (int i = 0; i < 10; i++) {
321321 // store the results of the previous iteration
322322 glm::mat3 previous = upper;
323−
323+
324324 // compute average of the matrix with its inverse transpose
325325 float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2];
326326 float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2];
@@ -334,15 +334,15 @@ glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { · 7 unmodified lines
334334 upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f;
335335 upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f;
336336 upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f;
337−
337+
338338 upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f;
339339 upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f;
340340 upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f;
341−
341+
342342 upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f;
343343 upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f;
344344 upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f;
345−
345+
346346 // compute the difference; if it's small enough, we're done
347347 glm::mat3 diff = upper - previous;
348348 if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] +
@@ -352,7 +352,7 @@ glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { · 3 unmodified lines
352352 }
353353 }
354354 }
355−
355+
356356 // now that we have a nice orthogonal matrix, we can extract the rotation quaternion
357357 // using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions
358358 float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]);
@@ -473,7 +473,7 @@ glm::mat4 createMatFromScaleQuatAndPos(const glm::vec3& scale, const glm::quat& · 114 unmodified lines
473473 glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f));
474474 }
475475
476−// cancel out roll
476+// cancel out roll
477477 glm::quat cancelOutRoll(const glm::quat& q) {
478478 glm::vec3 forward = q * Vectors::FRONT;
479479 return glm::quat_cast(glm::inverse(glm::lookAt(Vectors::ZERO, forward, Vectors::UP)));
@@ -538,17 +538,16 @@ void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& seconda · 58 unmodified lines
538538 uAxisOut = glm::normalize(primaryAxis);
539539 glm::vec3 normSecondary = glm::normalize(secondaryAxis);
540540
541− // if secondaryAxis is parallel with the primaryAxis, pick another axis.
541+ // if normSecondary is parallel with the primaryAxis, pick another secondary.
542542 const float EPSILON = 1.0e-4f;
543− if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) {
544− // pick a better secondaryAxis.
545− normSecondary = glm::vec3(1.0f, 0.0f, 0.0f);
546− if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) {
547− normSecondary = glm::vec3(0.0f, 1.0f, 0.0f);
543+ if (fabsf(fabsf(glm::dot(uAxisOut, normSecondary)) - 1.0f) < EPSILON) {
544+ normSecondary = Vectors::UNIT_X;
545+ if (fabsf(fabsf(glm::dot(uAxisOut, normSecondary)) - 1.0f) < EPSILON) {
546+ normSecondary = Vectors::UNIT_Y;
548547 }
549548 }
550549
551− wAxisOut = glm::normalize(glm::cross(uAxisOut, secondaryAxis));
550+ wAxisOut = glm::normalize(glm::cross(uAxisOut, normSecondary));
552551 vAxisOut = glm::cross(wAxisOut, uAxisOut);
553552 }
554553
libraries/shared/src/GeometryUtil.cpp+2−2
@@ -290,12 +290,12 @@ glm::vec3 Triangle::getNormal() const { · 289 unmodified lines
290290 }
291291
292292 bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
293− const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance) {
293+ const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) {
294294 glm::vec3 firstSide = v0 - v1;
295295 glm::vec3 secondSide = v2 - v1;
296296 glm::vec3 normal = glm::cross(secondSide, firstSide);
297297 float dividend = glm::dot(normal, v1) - glm::dot(origin, normal);
298− if (dividend > 0.0f) {
298+ if (!allowBackface && dividend > 0.0f) {
299299 return false; // origin below plane
300300 }
301301 float divisor = glm::dot(normal, direction);
libraries/shared/src/GeometryUtil.h+3−3
@@ -83,7 +83,7 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire · 82 unmodified lines
8383 const glm::vec3& position, const glm::vec2& dimensions, float& distance);
8484
8585 bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
86− const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance);
86+ const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface = false);
8787
8888 /// \brief decomposes rotation into its components such that: rotation = swing * twist
8989 /// \param rotation[in] rotation to decompose
@@ -104,8 +104,8 @@ public: · 14 unmodified lines
104104 };
105105
106106 inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
107− const Triangle& triangle, float& distance) {
108− return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance);
107+ const Triangle& triangle, float& distance, bool allowBackface = false) {
108+ return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance, allowBackface);
109109 }
110110
111111
libraries/shared/src/NumericalConstants.h+3−0
@@ -50,10 +50,13 @@ const int KILO_PER_MEGA = 1000; · 49 unmodified lines
5050
5151 #define KB_TO_BYTES_SHIFT 10
5252 #define MB_TO_BYTES_SHIFT 20
53+#define GB_TO_BYTES_SHIFT 30
5354
55+#define GB_TO_BYTES(X) ((size_t)(X) << GB_TO_BYTES_SHIFT)
5456 #define MB_TO_BYTES(X) ((size_t)(X) << MB_TO_BYTES_SHIFT)
5557 #define KB_TO_BYTES(X) ((size_t)(X) << KB_TO_BYTES_SHIFT)
5658
59+#define BYTES_TO_GB(X) (X >> GB_TO_BYTES_SHIFT)
5760 #define BYTES_TO_MB(X) (X >> MB_TO_BYTES_SHIFT)
5861 #define BYTES_TO_KB(X) (X >> KB_TO_BYTES_SHIFT)
5962
libraries/shared/src/RenderArgs.h+4−4
@@ -76,7 +76,7 @@ public: · 75 unmodified lines
7676 class RenderArgs {
7777 public:
7878 enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE };
79− enum RenderSide { MONO, STEREO_LEFT, STEREO_RIGHT };
79+ enum DisplayMode { MONO, STEREO_MONITOR, STEREO_HMD };
8080 enum DebugFlags {
8181 RENDER_DEBUG_NONE = 0,
8282 RENDER_DEBUG_HULLS = 1
@@ -87,7 +87,7 @@ public: · 4 unmodified lines
8787 float sizeScale = 1.0f,
8888 int boundaryLevelAdjust = 0,
8989 RenderMode renderMode = DEFAULT_RENDER_MODE,
90− RenderSide renderSide = MONO,
90+ DisplayMode displayMode = MONO,
9191 DebugFlags debugFlags = RENDER_DEBUG_NONE,
9292 gpu::Batch* batch = nullptr) :
9393 _context(context),
@@ -95,7 +95,7 @@ public: · 1 unmodified line
9595 _sizeScale(sizeScale),
9696 _boundaryLevelAdjust(boundaryLevelAdjust),
9797 _renderMode(renderMode),
98− _renderSide(renderSide),
98+ _displayMode(displayMode),
9999 _debugFlags(debugFlags),
100100 _batch(batch) {
101101 }
@@ -121,7 +121,7 @@ public: · 19 unmodified lines
121121 float _sizeScale = 1.0f;
122122 int _boundaryLevelAdjust = 0;
123123 RenderMode _renderMode = DEFAULT_RENDER_MODE;
124− RenderSide _renderSide = MONO;
124+ DisplayMode _displayMode = MONO;
125125 DebugFlags _debugFlags = RENDER_DEBUG_NONE;
126126 gpu::Batch* _batch = nullptr;
127127
libraries/shared/src/RunningMarker.cpp+1−34
@@ -13,44 +13,16 @@ · 12 unmodified lines
1313
1414 #include <QFile>
1515 #include <QStandardPaths>
16−#include <QThread>
17−#include <QTimer>
1816
19−#include "NumericalConstants.h"
2017 #include "PathUtils.h"
2118
22−RunningMarker::RunningMarker(QObject* parent, QString name) :
23− _parent(parent),
19+RunningMarker::RunningMarker(QString name) :
2420 _name(name)
2521 {
2622 }
2723
28−void RunningMarker::startRunningMarker() {
29− static const int RUNNING_STATE_CHECK_IN_MSECS = MSECS_PER_SECOND;
30−
31− // start the nodeThread so its event loop is running
32− _runningMarkerThread = new QThread(_parent);
33− _runningMarkerThread->setObjectName("Running Marker Thread");
34− _runningMarkerThread->start();
35−
36− writeRunningMarkerFile(); // write the first file, even before timer
37−
38− _runningMarkerTimer = new QTimer();
39− QObject::connect(_runningMarkerTimer, &QTimer::timeout, [=](){
40− writeRunningMarkerFile();
41− });
42− _runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS);
43−
44− // put the time on the thread
45− _runningMarkerTimer->moveToThread(_runningMarkerThread);
46−}
47−
4824 RunningMarker::~RunningMarker() {
4925 deleteRunningMarkerFile();
50− QMetaObject::invokeMethod(_runningMarkerTimer, "stop", Qt::BlockingQueuedConnection);
51− _runningMarkerThread->quit();
52− _runningMarkerTimer->deleteLater();
53− _runningMarkerThread->deleteLater();
5426 }
5527
5628 bool RunningMarker::fileExists() const {
@@ -77,8 +49,3 @@ void RunningMarker::deleteRunningMarkerFile() { · 20 unmodified lines
7749 QString RunningMarker::getFilePath() const {
7850 return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name;
7951 }
80−
81−QString RunningMarker::getMarkerFilePath(QString name) {
82− return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + name;
83−}
84−
libraries/shared/src/RunningMarker.h+1−11
@@ -12,21 +12,14 @@ · 11 unmodified lines
1212 #ifndef hifi_RunningMarker_h
1313 #define hifi_RunningMarker_h
1414
15−#include <QObject>
1615 #include <QString>
1716
18−class QThread;
19−class QTimer;
20−
2117 class RunningMarker {
2218 public:
23− RunningMarker(QObject* parent, QString name);
19+ RunningMarker(QString name);
2420 ~RunningMarker();
2521
26− void startRunningMarker();
27−
2822 QString getFilePath() const;
29− static QString getMarkerFilePath(QString name);
3023
3124 bool fileExists() const;
3225
@@ -34,10 +27,7 @@ public: · 1 unmodified line
3427 void deleteRunningMarkerFile();
3528
3629 private:
37− QObject* _parent { nullptr };
3830 QString _name;
39− QThread* _runningMarkerThread { nullptr };
40− QTimer* _runningMarkerTimer { nullptr };
4131 };
4232
4333 #endif // hifi_RunningMarker_h
libraries/shared/src/TBBHelpers.h+28−0
@@ -0,0 +1,28 @@
1+//
2+// Created by Bradley Austin Davis on 2017/06/06
3+// Copyright 2013-2017 High Fidelity, Inc.
4+//
5+// Distributed under the Apache License, Version 2.0.
6+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
7+//
8+
9+#pragma once
10+#ifndef hifi_TBBHelpers_h
11+#define hifi_TBBHelpers_h
12+
13+#ifdef _WIN32
14+#pragma warning( push )
15+#pragma warning( disable : 4334 )
16+#endif
17+
18+#include <tbb/concurrent_queue.h>
19+#include <tbb/concurrent_unordered_map.h>
20+#include <tbb/concurrent_unordered_set.h>
21+#include <tbb/concurrent_vector.h>
22+#include <tbb/parallel_for.h>
23+
24+#ifdef _WIN32
25+#pragma warning( pop )
26+#endif
27+
28+#endif // hifi_TBBHelpers_h
libraries/shared/src/ThreadHelpers.cpp+39−0
@@ -0,0 +1,39 @@
1+//
2+// Created by Bradley Austin Davis on 2017/06/06
3+// Copyright 2013-2017 High Fidelity, Inc.
4+//
5+// Distributed under the Apache License, Version 2.0.
6+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
7+//
8+
9+#include "ThreadHelpers.h"
10+
11+#include <QtCore/QDebug>
12+
13+
14+void moveToNewNamedThread(QObject* object, const QString& name, std::function<void()> startCallback, QThread::Priority priority) {
15+ Q_ASSERT(QThread::currentThread() == object->thread());
16+ // setup a thread for the NodeList and its PacketReceiver
17+ QThread* thread = new QThread();
18+ thread->setObjectName(name);
19+
20+ if (priority != QThread::InheritPriority) {
21+ thread->setPriority(priority);
22+ }
23+
24+ QString tempName = name;
25+ QObject::connect(thread, &QThread::started, [startCallback] {
26+ startCallback();
27+ });
28+ // Make sure the thread will be destroyed and cleaned up
29+ QObject::connect(object, &QObject::destroyed, thread, &QThread::quit);
30+ QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
31+
32+ // put the object on the thread
33+ object->moveToThread(thread);
34+ thread->start();
35+}
36+
37+void moveToNewNamedThread(QObject* object, const QString& name, QThread::Priority priority) {
38+ moveToNewNamedThread(object, name, [] {}, priority);
39+}
libraries/shared/src/ThreadHelpers.h+10−2
@@ -12,8 +12,13 @@ · 11 unmodified lines
1212 #define hifi_ThreadHelpers_h
1313
1414 #include <exception>
15−#include <QMutex>
16−#include <QMutexLocker>
15+#include <functional>
16+
17+#include <QtCore/QMutex>
18+#include <QtCore/QMutexLocker>
19+#include <QtCore/QObject>
20+#include <QtCore/QString>
21+#include <QtCore/QThread>
1722
1823 template <typename L, typename F>
1924 void withLock(L lock, F function) {
@@ -26,4 +31,7 @@ void withLock(QMutex& lock, F function) { · 6 unmodified lines
2631 function();
2732 }
2833
34+void moveToNewNamedThread(QObject* object, const QString& name, std::function<void()> startCallback, QThread::Priority priority = QThread::InheritPriority);
35+void moveToNewNamedThread(QObject* object, const QString& name, QThread::Priority priority = QThread::InheritPriority);
36+
2937 #endif
libraries/shared/src/TriangleSet.cpp+6−6
@@ -31,7 +31,7 @@ void TriangleSet::clear() { · 30 unmodified lines
3131 }
3232
3333 bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
34− float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) {
34+ float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface) {
3535
3636 // reset our distance to be the max possible, lower level tests will store best distance here
3737 distance = std::numeric_limits<float>::max();
@@ -41,7 +41,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& · 3 unmodified lines
4141 }
4242
4343 int trianglesTouched = 0;
44− auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched);
44+ auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched, allowBackface);
4545
4646 #if WANT_DEBUGGING
4747 if (precision) {
@@ -95,7 +95,7 @@ void TriangleSet::balanceOctree() { · 47 unmodified lines
9595 // Determine of the given ray (origin/direction) in model space intersects with any triangles
9696 // in the set. If an intersection occurs, the distance and surface normal will be provided.
9797 bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
98− float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) {
98+ float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) {
9999
100100 bool intersectedSomething = false;
101101 float boxDistance = distance;
@@ -114,7 +114,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec · 12 unmodified lines
114114 const auto& triangle = _allTriangles[triangleIndex];
115115 float thisTriangleDistance;
116116 trianglesTouched++;
117− if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) {
117+ if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance, allowBackface)) {
118118 if (thisTriangleDistance < bestDistance) {
119119 bestDistance = thisTriangleDistance;
120120 intersectedSomething = true;
@@ -203,7 +203,7 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { · 82 unmodified lines
203203 }
204204
205205 bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
206− float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) {
206+ float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) {
207207
208208 if (_population < 1) {
209209 return false; // no triangles below here, so we can't intersect
@@ -247,7 +247,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi · 37 unmodified lines
247247 }
248248 }
249249 // also check our local triangle set
250− if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) {
250+ if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched, allowBackface)) {
251251 if (childDistance < bestLocalDistance) {
252252 bestLocalDistance = childDistance;
253253 bestLocalFace = childFace;
libraries/shared/src/TriangleSet.h+3−3
@@ -27,7 +27,7 @@ class TriangleSet { · 26 unmodified lines
2727 void clear();
2828
2929 bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
30− float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched);
30+ float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false);
3131
3232 const AABox& getBounds() const { return _bounds; }
3333
@@ -38,7 +38,7 @@ class TriangleSet { · 4 unmodified lines
3838
3939 // checks our internal list of triangles
4040 bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
41− float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched);
41+ float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false);
4242
4343 std::vector<Triangle>& _allTriangles;
4444 std::map<AABox::OctreeChild, TriangleOctreeCell> _children;
@@ -60,7 +60,7 @@ public: · 15 unmodified lines
6060 void insert(const Triangle& t);
6161
6262 bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
63− float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision);
63+ float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface = false);
6464
6565 void balanceOctree();
6666
libraries/ui/src/OffscreenUi.cpp+8−8
@@ -91,14 +91,14 @@ QObject* OffscreenUi::getFlags() { · 90 unmodified lines
9191
9292 void OffscreenUi::create(QOpenGLContext* context) {
9393 OffscreenQmlSurface::create(context);
94− auto rootContext = getRootContext();
94+ auto myContext = getSurfaceContext();
9595
96− rootContext->setContextProperty("OffscreenUi", this);
97− rootContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
98− rootContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
96+ myContext->setContextProperty("OffscreenUi", this);
97+ myContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
98+ myContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
9999 auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
100100 TabletProxy* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");
101− rootContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership);
101+ myContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership);
102102 }
103103
104104 void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
@@ -547,14 +547,14 @@ void OffscreenUi::createDesktop(const QUrl& url) { · 442 unmodified lines
547547 }
548548
549549 #ifdef DEBUG
550− getRootContext()->setContextProperty("DebugQML", QVariant(true));
550+ getSurfaceContext()->setContextProperty("DebugQML", QVariant(true));
551551 #else
552− getRootContext()->setContextProperty("DebugQML", QVariant(false));
552+ getSurfaceContext()->setContextProperty("DebugQML", QVariant(false));
553553 #endif
554554
555555 _desktop = dynamic_cast<QQuickItem*>(load(url));
556556 Q_ASSERT(_desktop);
557− getRootContext()->setContextProperty("desktop", _desktop);
557+ getSurfaceContext()->setContextProperty("desktop", _desktop);
558558
559559 _toolWindow = _desktop->findChild<QQuickItem*>("ToolWindow");
560560
libraries/ui/src/QmlWindowClass.cpp+2−2
@@ -104,9 +104,9 @@ void QmlWindowClass::initQml(QVariantMap properties) { · 103 unmodified lines
104104 Q_ASSERT(invokeResult);
105105 } else {
106106 // Build the event bridge and wrapper on the main thread
107− offscreenUi->load(qmlSource(), [&](QQmlContext* context, QObject* object) {
107+ offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) {
108108 _qmlWindow = object;
109− _qmlWindow->setProperty("eventBridge", QVariant::fromValue(this));
109+ context->setContextProperty("eventBridge", this);
110110 context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
111111 context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership);
112112 if (properties.contains(TITLE_PROPERTY)) {
libraries/ui/src/VrMenu.cpp+1−1
@@ -115,7 +115,7 @@ private: · 114 unmodified lines
115115
116116 VrMenu::VrMenu(OffscreenUi* parent) : QObject(parent) {
117117 _rootMenu = parent->getRootItem()->findChild<QObject*>("rootMenu");
118− parent->getRootContext()->setContextProperty("rootMenu", _rootMenu);
118+ parent->getSurfaceContext()->setContextProperty("rootMenu", _rootMenu);
119119 }
120120
121121 QObject* VrMenu::findMenuObject(const QString& menuOption) {
plugins/hifiKinect/src/KinectPlugin.cpp+6−2
@@ -273,7 +273,7 @@ bool KinectPlugin::activate() { · 272 unmodified lines
273273 return false;
274274 }
275275
276−bool KinectPlugin::isHandController() const {
276+bool KinectPlugin::isHandController() const {
277277 bool sensorAvailable = false;
278278 #ifdef HAVE_KINECT
279279 if (_kinectSensor) {
@@ -285,6 +285,10 @@ bool KinectPlugin::isHandController() const { · 5 unmodified lines
285285 return _enabled && _initialized && sensorAvailable;
286286 }
287287
288+bool KinectPlugin::isHeadController() const {
289+ return isHandController();
290+}
291+
288292
289293 bool KinectPlugin::initializeDefaultSensor() const {
290294 #ifdef HAVE_KINECT
@@ -654,4 +658,4 @@ void KinectPlugin::InputDevice::clearState() { · 363 unmodified lines
654658 int poseIndex = KinectJointIndexToPoseIndex((KinectJointIndex)i);
655659 _poseStateMap[poseIndex] = controller::Pose();
656660 }
657−}
661+}
plugins/hifiKinect/src/KinectPlugin.h+1−0
@@ -43,6 +43,7 @@ class KinectPlugin : public InputPlugin { · 42 unmodified lines
4343 Q_OBJECT
4444 public:
4545 bool isHandController() const override;
46+ bool isHeadController() const override;
4647
4748 // Plugin functions
4849 virtual void init() override;
plugins/hifiNeuron/src/NeuronPlugin.h+0−2
@@ -25,8 +25,6 @@ class NeuronPlugin : public InputPlugin { · 24 unmodified lines
2525 public:
2626 friend void FrameDataReceivedCallback(void* context, void* sender, _BvhDataHeaderEx* header, float* data);
2727
28− bool isHandController() const override { return false; }
29−
3028 // Plugin functions
3129 virtual void init() override;
3230 virtual bool isSupported() const override;
plugins/hifiSdl2/src/SDL2Manager.h+0−1
@@ -27,7 +27,6 @@ public: · 26 unmodified lines
2727 const QString getName() const override { return NAME; }
2828
2929 QStringList getSubdeviceNames() override;
30− bool isHandController() const override { return false; }
3130
3231 void init() override;
3332 void deinit() override;
plugins/hifiSixense/src/SixenseManager.h+1−1
@@ -33,7 +33,7 @@ public: · 32 unmodified lines
3333
3434 // Sixense always seems to initialize even if the hydras are not present. Is there
3535 // a way we can properly detect whether the hydras are present?
36− bool isHandController() const override { return false; }
36+ // bool isHandController() const override { return true; }
3737
3838 virtual bool activate() override;
3939 virtual void deactivate() override;
plugins/oculus/src/OculusBaseDisplayPlugin.cpp+10−1
@@ -123,10 +123,19 @@ bool OculusBaseDisplayPlugin::internalActivate() { · 122 unmodified lines
123123
124124 void OculusBaseDisplayPlugin::internalDeactivate() {
125125 Parent::internalDeactivate();
126+}
127+
128+bool OculusBaseDisplayPlugin::activateStandBySession() {
129+ _session = acquireOculusSession();
130+ if (!_session) {
131+ return false;
132+ }
133+ return true;
134+}
135+void OculusBaseDisplayPlugin::deactivateSession() {
126136 releaseOculusSession();
127137 _session = nullptr;
128138 }
129−
130139 void OculusBaseDisplayPlugin::updatePresentPose() {
131140 //mat4 sensorResetMat;
132141 //_currentPresentFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds();
plugins/oculus/src/OculusBaseDisplayPlugin.h+3−0
@@ -26,6 +26,7 @@ public: · 25 unmodified lines
2626 void resetSensors() override final;
2727 bool beginFrameRender(uint32_t frameIndex) override;
2828 float getTargetFrameRate() const override { return _hmdDesc.DisplayRefreshRate; }
29+ bool getSupportsAutoSwitch() override final { return true; }
2930
3031
3132 protected:
@@ -33,6 +34,8 @@ protected: · 1 unmodified line
3334 void uncustomizeContext() override;
3435 bool internalActivate() override;
3536 void internalDeactivate() override;
37+ bool activateStandBySession() override;
38+ void deactivateSession() override;
3639 void updatePresentPose() override;
3740
3841 protected:
plugins/oculus/src/OculusControllerManager.cpp+48−11
@@ -19,6 +19,8 @@ · 18 unmodified lines
1919
2020 #include <PerfStat.h>
2121 #include <PathUtils.h>
22+#include <NumericalConstants.h>
23+#include <StreamUtils.h>
2224
2325 #include <OVR_CAPI.h>
2426
@@ -208,13 +210,18 @@ void OculusControllerManager::RemoteDevice::focusOutEvent() { · 183 unmodified lines
208210 _buttonPressedMap.clear();
209211 }
210212
211−void OculusControllerManager::TouchDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
212− _buttonPressedMap.clear();
213+bool OculusControllerManager::isHeadControllerMounted() const {
213214 ovrSessionStatus status;
214− if (!OVR_SUCCESS(ovr_GetSessionStatus(_parent._session, &status)) || (ovrFalse == status.HmdMounted)) {
215− // if the HMD isn't on someone's head, don't take input from the controllers
216− return;
215+ bool success = OVR_SUCCESS(ovr_GetSessionStatus(_session, &status));
216+ if (!success) {
217+ return false;
217218 }
219+ return status.HmdMounted == ovrTrue;
220+}
221+
222+void OculusControllerManager::TouchDevice::update(float deltaTime,
223+ const controller::InputCalibrationData& inputCalibrationData) {
224+ _buttonPressedMap.clear();
218225
219226 int numTrackedControllers = 0;
220227 quint64 currentTime = usecTimestampNow();
@@ -230,14 +237,14 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control · 9 unmodified lines
230237 _lastControllerPose[controller] = tracking.HandPoses[hand];
231238 return;
232239 }
233−
240+
234241 if (_lostTracking[controller]) {
235242 if (currentTime > _regainTrackingDeadline[controller]) {
236243 _poseStateMap.erase(controller);
237244 _poseStateMap[controller].valid = false;
238245 return;
239246 }
240−
247+
241248 } else {
242249 quint64 deadlineToRegainTracking = currentTime + LOST_TRACKING_DELAY;
243250 _regainTrackingDeadline[controller] = deadlineToRegainTracking;
@@ -245,6 +252,13 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control · 1 unmodified line
245252 }
246253 handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HandPoses[hand]);
247254 });
255+
256+ if (_parent.isHeadControllerMounted()) {
257+ handleHeadPose(deltaTime, inputCalibrationData, tracking.HeadPose);
258+ } else {
259+ _poseStateMap[controller::HEAD].valid = false;
260+ }
261+
248262 using namespace controller;
249263 // Axes
250264 const auto& inputState = _parent._inputState;
@@ -269,7 +283,7 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control · 18 unmodified lines
269283 if (inputState.Touches & pair.first) {
270284 _buttonPressedMap.insert(pair.second);
271285 }
272− }
286+ }
273287
274288 // Haptics
275289 {
@@ -292,16 +306,38 @@ void OculusControllerManager::TouchDevice::focusOutEvent() { · 16 unmodified lines
292306 _buttonPressedMap.clear();
293307 };
294308
295−void OculusControllerManager::TouchDevice::handlePose(float deltaTime,
296− const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand,
297− const ovrPoseStatef& handPose) {
309+void OculusControllerManager::TouchDevice::handlePose(float deltaTime,
310+ const controller::InputCalibrationData& inputCalibrationData,
311+ ovrHandType hand, const ovrPoseStatef& handPose) {
298312 auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND;
299313 auto& pose = _poseStateMap[poseId];
300314 pose = ovrControllerPoseToHandPose(hand, handPose);
301315 // transform into avatar frame
302316 glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
303317 pose = pose.transform(controllerToAvatar);
318+}
319+
320+void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime,
321+ const controller::InputCalibrationData& inputCalibrationData,
322+ const ovrPoseStatef& headPose) {
323+ glm::mat4 mat = createMatFromQuatAndPos(toGlm(headPose.ThePose.Orientation),
324+ toGlm(headPose.ThePose.Position));
325+
326+ //perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z
327+ glm::mat4 matYFlip = mat * Matrices::Y_180;
328+ controller::Pose pose(extractTranslation(matYFlip),
329+ glmExtractRotation(matYFlip),
330+ toGlm(headPose.LinearVelocity), // XXX * matYFlip ?
331+ toGlm(headPose.AngularVelocity));
332+
333+ glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
334+ glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) *
335+ inputCalibrationData.defaultHeadMat;
336+
337+ controller::Pose hmdHeadPose = pose.transform(sensorToAvatar);
304338
339+ pose.valid = true;
340+ _poseStateMap[controller::HEAD] = hmdHeadPose.postTransform(defaultHeadOffset);
305341 }
306342
307343 void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData,
@@ -382,6 +418,7 @@ controller::Input::NamedVector OculusControllerManager::TouchDevice::getAvailabl · 74 unmodified lines
382418
383419 makePair(LEFT_HAND, "LeftHand"),
384420 makePair(RIGHT_HAND, "RightHand"),
421+ makePair(HEAD, "Head"),
385422
386423 makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"),
387424 makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"),
plugins/oculus/src/OculusControllerManager.h+9−2
@@ -28,6 +28,8 @@ public: · 27 unmodified lines
2828 const QString getName() const override { return NAME; }
2929
3030 bool isHandController() const override { return _touch != nullptr; }
31+ bool isHeadController() const override { return true; }
32+ bool isHeadControllerMounted() const;
3133 QStringList getSubdeviceNames() override;
3234
3335 bool activate() override;
@@ -75,8 +77,13 @@ private: · 41 unmodified lines
7577
7678 private:
7779 void stopHapticPulse(bool leftHand);
78− void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose);
79− void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose);
80+ void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
81+ ovrHandType hand, const ovrPoseStatef& handPose);
82+ void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData,
83+ ovrHandType hand, const ovrPoseStatef& handPose);
84+ void handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
85+ const ovrPoseStatef& headPose);
86+
8087 int _trackedControllers { 0 };
8188
8289 // perform an action when the TouchDevice mutex is acquired.
plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp+1−1
@@ -24,7 +24,7 @@ · 23 unmodified lines
2424 #include <gl/QOpenGLContextWrapper.h>
2525 #include <PerfStat.h>
2626 #include <ViewFrustum.h>
27−#include <gpu/gl/GLbackend.h>
27+#include <gpu/gl/GLBackend.h>
2828
2929 #include <ui-plugins/PluginContainer.h>
3030
plugins/openvr/CMakeLists.txt+3−2
@@ -10,10 +10,10 @@ if (WIN32) · 9 unmodified lines
1010 # we're using static GLEW, so define GLEW_STATIC
1111 add_definitions(-DGLEW_STATIC)
1212 set(TARGET_NAME openvr)
13− setup_hifi_plugin(OpenGL Script Qml Widgets)
13+ setup_hifi_plugin(OpenGL Script Qml Widgets Multimedia)
1414 link_hifi_libraries(shared gl networking controllers ui
1515 plugins display-plugins ui-plugins input-plugins script-engine
16− render-utils model gpu gpu-gl render model-networking fbx ktx image)
16+ audio-client render-utils model gpu gpu-gl render model-networking fbx ktx image procedural)
1717
1818 include_hifi_library_headers(octree)
1919
@@ -21,4 +21,5 @@ if (WIN32) · 1 unmodified line
2121 find_package(OpenVR REQUIRED)
2222 target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS})
2323 target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES})
24+ target_link_libraries(${TARGET_NAME} Winmm.lib)
2425 endif()
plugins/openvr/src/OpenVrDisplayPlugin.cpp+30−0
@@ -7,6 +7,9 @@ · 6 unmodified lines
77 //
88 #include "OpenVrDisplayPlugin.h"
99
10+// Odd ordering of header is required to avoid 'macro redinition warnings'
11+#include <AudioClient.h>
12+
1013 #include <QtCore/QThread>
1114 #include <QtCore/QLoggingCategory>
1215 #include <QtCore/QFileInfo>
@@ -713,3 +716,30 @@ bool OpenVrDisplayPlugin::isKeyboardVisible() { · 700 unmodified lines
713716 int OpenVrDisplayPlugin::getRequiredThreadCount() const {
714717 return Parent::getRequiredThreadCount() + (_threadedSubmit ? 1 : 0);
715718 }
719+
720+QString OpenVrDisplayPlugin::getPreferredAudioInDevice() const {
721+ QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_OnPlaybackDevice_String);
722+ if (!device.isEmpty()) {
723+ static const WCHAR INIT = 0;
724+ size_t size = device.size() + 1;
725+ std::vector<WCHAR> deviceW;
726+ deviceW.assign(size, INIT);
727+ device.toWCharArray(deviceW.data());
728+ device = AudioClient::friendlyNameForAudioDevice(deviceW.data());
729+ }
730+ return device;
731+}
732+
733+QString OpenVrDisplayPlugin::getPreferredAudioOutDevice() const {
734+ QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_OnRecordDevice_String);
735+ if (!device.isEmpty()) {
736+ static const WCHAR INIT = 0;
737+ size_t size = device.size() + 1;
738+ std::vector<WCHAR> deviceW;
739+ deviceW.assign(size, INIT);
740+ device.toWCharArray(deviceW.data());
741+ device = AudioClient::friendlyNameForAudioDevice(deviceW.data());
742+ }
743+ return device;
744+}
745+
plugins/openvr/src/OpenVrDisplayPlugin.h+4−1
@@ -56,7 +56,10 @@ public: · 55 unmodified lines
5656 bool isKeyboardVisible() override;
5757
5858 // Possibly needs an additional thread for VR submission
59− int getRequiredThreadCount() const override;
59+ int getRequiredThreadCount() const override;
60+
61+ QString getPreferredAudioInDevice() const override;
62+ QString getPreferredAudioOutDevice() const override;
6063
6164 protected:
6265 bool internalActivate() override;
plugins/openvr/src/OpenVrHelpers.cpp+16−0
@@ -72,6 +72,21 @@ bool openVrSupported() { · 71 unmodified lines
7272 return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent();
7373 }
7474
75+QString getVrSettingString(const char* section, const char* setting) {
76+ QString result;
77+ static const uint32_t BUFFER_SIZE = 1024;
78+ static char BUFFER[BUFFER_SIZE];
79+ vr::IVRSettings * vrSettings = vr::VRSettings();
80+ if (vrSettings) {
81+ vr::EVRSettingsError error = vr::VRSettingsError_None;
82+ vrSettings->GetString(vr::k_pch_audio_Section, vr::k_pch_audio_OnPlaybackDevice_String, BUFFER, BUFFER_SIZE, &error);
83+ if (error == vr::VRSettingsError_None) {
84+ result = BUFFER;
85+ }
86+ }
87+ return result;
88+}
89+
7590 vr::IVRSystem* acquireOpenVrSystem() {
7691 bool hmdPresent = vr::VR_IsHmdPresent();
7792 if (hmdPresent) {
@@ -82,6 +97,7 @@ vr::IVRSystem* acquireOpenVrSystem() { · 4 unmodified lines
8297 #endif
8398 vr::EVRInitError eError = vr::VRInitError_None;
8499 activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene);
100+
85101 #if DEV_BUILD
86102 qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError;
87103 #endif
plugins/openvr/src/OpenVrHelpers.h+1−0
@@ -25,6 +25,7 @@ bool openVrQuitRequested(); · 24 unmodified lines
2525 void enableOpenVrKeyboard(PluginContainer* container);
2626 void disableOpenVrKeyboard();
2727 bool isOpenVrKeyboardShown();
28+QString getVrSettingString(const char* section, const char* setting);
2829
2930
3031 template<typename F>
plugins/openvr/src/ViveControllerManager.cpp+174−8
@@ -50,11 +50,15 @@ static const char* RENDER_CONTROLLERS = "Render Hand Controllers"; · 49 unmodified lines
5050 static const int MIN_PUCK_COUNT = 2;
5151 static const int MIN_FEET_AND_HIPS = 3;
5252 static const int MIN_FEET_HIPS_CHEST = 4;
53+static const int MIN_FEET_HIPS_HEAD = 4;
5354 static const int MIN_FEET_HIPS_SHOULDERS = 5;
55+static const int MIN_FEET_HIPS_CHEST_HEAD = 5;
5456 static const int FIRST_FOOT = 0;
5557 static const int SECOND_FOOT = 1;
5658 static const int HIP = 2;
5759 static const int CHEST = 3;
60+static float HEAD_PUCK_Y_OFFSET = -0.0254f;
61+static float HEAD_PUCK_Z_OFFSET = -0.152f;
5862
5963 const char* ViveControllerManager::NAME { "OpenVR" };
6064
@@ -80,6 +84,28 @@ static bool sortPucksXPosition(PuckPosePair firstPuck, PuckPosePair secondPuck) · 19 unmodified lines
8084 return (firstPuck.second.translation.x < secondPuck.second.translation.x);
8185 }
8286
87+static bool determineFeetOrdering(const controller::Pose& poseA, const controller::Pose& poseB, glm::vec3 axis, glm::vec3 axisOrigin) {
88+ glm::vec3 poseAPosition = poseA.getTranslation();
89+ glm::vec3 poseBPosition = poseB.getTranslation();
90+
91+ glm::vec3 poseAVector = poseAPosition - axisOrigin;
92+ glm::vec3 poseBVector = poseBPosition - axisOrigin;
93+
94+ float poseAProjection = glm::dot(poseAVector, axis);
95+ float poseBProjection = glm::dot(poseBVector, axis);
96+ return (poseAProjection > poseBProjection);
97+}
98+
99+static glm::vec3 getReferenceHeadXAxis(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) {
100+ glm::mat4 finalHead = defaultToReferenceMat * defaultHead;
101+ return glmExtractRotation(finalHead) * Vectors::UNIT_X;
102+}
103+
104+static glm::vec3 getReferenceHeadPosition(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) {
105+ glm::mat4 finalHead = defaultToReferenceMat * defaultHead;
106+ return extractTranslation(finalHead);
107+}
108+
83109 static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) {
84110 QString result;
85111 auto iterator = TRACKING_RESULT_TO_STRING.find(trackingResult);
@@ -97,15 +123,18 @@ bool ViveControllerManager::isSupported() const { · 11 unmodified lines
97123 bool ViveControllerManager::activate() {
98124 InputPlugin::activate();
99125
100− _container->addMenu(MENU_PATH);
101− _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS,
102− [this] (bool clicked) { this->setRenderControllers(clicked); },
103− true, true);
104−
105126 if (!_system) {
106127 _system = acquireOpenVrSystem();
107128 }
108− Q_ASSERT(_system);
129+
130+ if (!_system) {
131+ return false;
132+ }
133+
134+ _container->addMenu(MENU_PATH);
135+ _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS,
136+ [this](bool clicked) { this->setRenderControllers(clicked); },
137+ true, true);
109138
110139 enableOpenVrKeyboard(_container);
111140
@@ -138,6 +167,14 @@ void ViveControllerManager::deactivate() { · 26 unmodified lines
138167 _registeredWithInputMapper = false;
139168 }
140169
170+bool ViveControllerManager::isHeadControllerMounted() const {
171+ if (_inputDevice && _inputDevice->isHeadControllerMounted()) {
172+ return true;
173+ }
174+ vr::EDeviceActivityLevel activityLevel = _system->GetTrackedDeviceActivityLevel(vr::k_unTrackedDeviceIndex_Hmd);
175+ return activityLevel == vr::k_EDeviceActivityLevel_UserInteraction;
176+}
177+
141178 void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
142179
143180 if (!_system) {
@@ -175,6 +212,8 @@ ViveControllerManager::InputDevice::InputDevice(vr::IVRSystem*& system) : contro · 31 unmodified lines
175212 _configStringMap[Config::FeetAndHips] = QString("FeetAndHips");
176213 _configStringMap[Config::FeetHipsAndChest] = QString("FeetHipsAndChest");
177214 _configStringMap[Config::FeetHipsAndShoulders] = QString("FeetHipsAndShoulders");
215+ _configStringMap[Config::FeetHipsChestAndHead] = QString("FeetHipsChestAndHead");
216+ _configStringMap[Config::FeetHipsAndHead] = QString("FeetHipsAndHead");
178217
179218 if (openVrSupported()) {
180219 createPreferences();
@@ -355,6 +394,23 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr · 174 unmodified lines
355394 int firstShoulderIndex = 3;
356395 int secondShoulderIndex = 4;
357396 calibrateShoulders(defaultToReferenceMat, inputCalibration, firstShoulderIndex, secondShoulderIndex);
397+ } else if (_config == Config::FeetHipsAndHead && puckCount == MIN_FEET_HIPS_HEAD) {
398+ glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration);
399+ glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
400+ glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
401+ calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition);
402+ calibrateHips(headPuckDefaultToReferenceMat, inputCalibration);
403+ calibrateHead(headPuckDefaultToReferenceMat, inputCalibration);
404+ _overrideHead = true;
405+ } else if (_config == Config::FeetHipsChestAndHead && puckCount == MIN_FEET_HIPS_CHEST_HEAD) {
406+ glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration);
407+ glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
408+ glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
409+ calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition);
410+ calibrateHips(headPuckDefaultToReferenceMat, inputCalibration);
411+ calibrateChest(headPuckDefaultToReferenceMat, inputCalibration);
412+ calibrateHead(headPuckDefaultToReferenceMat, inputCalibration);
413+ _overrideHead = true;
358414 } else {
359415 qDebug() << "Puck Calibration: " << configToString(_config) << " Config Failed: Could not meet the minimal # of pucks";
360416 uncalibrate();
@@ -369,6 +425,7 @@ void ViveControllerManager::InputDevice::uncalibrate() { · 8 unmodified lines
369425 _pucksOffset.clear();
370426 _jointToPuckMap.clear();
371427 _calibrated = false;
428+ _overrideHead = false;
372429 }
373430
374431 void ViveControllerManager::InputDevice::updateCalibratedLimbs() {
@@ -378,6 +435,10 @@ void ViveControllerManager::InputDevice::updateCalibratedLimbs() { · 3 unmodified lines
378435 _poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2);
379436 _poseStateMap[controller::RIGHT_ARM] = addOffsetToPuckPose(controller::RIGHT_ARM);
380437 _poseStateMap[controller::LEFT_ARM] = addOffsetToPuckPose(controller::LEFT_ARM);
438+
439+ if (_overrideHead) {
440+ _poseStateMap[controller::HEAD] = addOffsetToPuckPose(controller::HEAD);
441+ }
381442 }
382443
383444 controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) const {
@@ -443,6 +504,43 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u · 59 unmodified lines
443504 }
444505 }
445506
507+glm::mat4 ViveControllerManager::InputDevice::recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration) {
508+ glm::mat4 avatarToSensorMat = glm::inverse(inputCalibration.sensorToWorldMat) * inputCalibration.avatarMat;
509+ glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat;
510+ size_t headPuckIndex = _validTrackedObjects.size() - 1;
511+ controller::Pose headPuckPose = _validTrackedObjects[headPuckIndex].second;
512+ glm::mat4 headPuckAvatarMat = createMatFromQuatAndPos(headPuckPose.getRotation(), headPuckPose.getTranslation()) * Matrices::Y_180;
513+ glm::vec3 headPuckTranslation = extractTranslation(headPuckAvatarMat);
514+ glm::vec3 headPuckZAxis = cancelOutRollAndPitch(glmExtractRotation(headPuckAvatarMat)) * glm::vec3(0.0f, 0.0f, 1.0f);
515+ glm::vec3 worldUp = glm::vec3(0.0f, 1.0f, 0.0f);
516+
517+ // check that the head puck z axis is not parrallel to the world up
518+ const float EPSILON = 1.0e-4f;
519+ glm::vec3 zAxis = glmExtractRotation(headPuckAvatarMat) * glm::vec3(0.0f, 0.0f, 1.0f);
520+ if (fabsf(fabsf(glm::dot(glm::normalize(worldUp), glm::normalize(zAxis))) - 1.0f) < EPSILON) {
521+ headPuckZAxis = glm::vec3(1.0f, 0.0f, 0.0f);
522+ }
523+
524+ glm::vec3 yPrime = glm::vec3(0.0f, 1.0f, 0.0f);
525+ glm::vec3 xPrime = glm::normalize(glm::cross(worldUp, headPuckZAxis));
526+ glm::vec3 zPrime = glm::normalize(glm::cross(xPrime, yPrime));
527+ glm::mat4 newHeadPuck = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f),
528+ glm::vec4(zPrime, 0.0f), glm::vec4(headPuckTranslation, 1.0f));
529+
530+ glm::mat4 headPuckOffset = glm::mat4(glm::vec4(1.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 1.0f, 0.0f, 0.0f),
531+ glm::vec4(0.0f, 0.0f, 1.0f, 0.0f), glm::vec4(0.0f, HEAD_PUCK_Y_OFFSET, HEAD_PUCK_Z_OFFSET, 1.0f));
532+
533+ glm::mat4 finalHeadPuck = newHeadPuck * headPuckOffset;
534+
535+ glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat;
536+
537+ glm::mat4 currentHead = finalHeadPuck * defaultHeadOffset;
538+
539+ // calculate the defaultToRefrenceXform
540+ glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat);
541+ return defaultToReferenceMat;
542+}
543+
446544 void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) {
447545 // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values.
448546 const float CENTER_DEADBAND = 0.6f;
@@ -630,6 +728,26 @@ void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToRefer · 181 unmodified lines
630728 }
631729 }
632730
731+
732+void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition) {
733+ auto& firstFoot = _validTrackedObjects[FIRST_FOOT];
734+ auto& secondFoot = _validTrackedObjects[SECOND_FOOT];
735+ controller::Pose& firstFootPose = firstFoot.second;
736+ controller::Pose& secondFootPose = secondFoot.second;
737+
738+ if (determineFeetOrdering(firstFootPose, secondFootPose, headXAxis, headPosition)) {
739+ _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first;
740+ _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, firstFootPose);
741+ _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first;
742+ _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, secondFootPose);
743+ } else {
744+ _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first;
745+ _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, secondFootPose);
746+ _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first;
747+ _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, firstFootPose);
748+ }
749+}
750+
633751 void ViveControllerManager::InputDevice::calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) {
634752 _jointToPuckMap[controller::HIPS] = _validTrackedObjects[HIP].first;
635753 _pucksOffset[_validTrackedObjects[HIP].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[HIP].second);
@@ -658,7 +776,19 @@ void ViveControllerManager::InputDevice::calibrateShoulders(glm::mat4& defaultTo · 22 unmodified lines
658776 _jointToPuckMap[controller::RIGHT_ARM] = firstShoulder.first;
659777 _pucksOffset[firstShoulder.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightArm, firstShoulder.second);
660778 }
661−}
779+}
780+
781+void ViveControllerManager::InputDevice::calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) {
782+ size_t headIndex = _validTrackedObjects.size() - 1;
783+ const PuckPosePair& head = _validTrackedObjects[headIndex];
784+
785+ // assume the person is wearing the head puck on his/her forehead
786+ glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat;
787+ controller::Pose newHead = head.second.postTransform(defaultHeadOffset);
788+
789+ _jointToPuckMap[controller::HEAD] = head.first;
790+ _pucksOffset[head.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHeadMat, newHead);
791+}
662792
663793
664794 void ViveControllerManager::InputDevice::loadSettings() {
@@ -694,6 +824,10 @@ void ViveControllerManager::InputDevice::setConfigFromString(const QString& valu · 29 unmodified lines
694824 _preferedConfig = Config::FeetHipsAndChest;
695825 } else if (value == "FeetHipsAndShoulders") {
696826 _preferedConfig = Config::FeetHipsAndShoulders;
827+ } else if (value == "FeetHipsChestAndHead") {
828+ _preferedConfig = Config::FeetHipsChestAndHead;
829+ } else if (value == "FeetHipsAndHead") {
830+ _preferedConfig = Config::FeetHipsAndHead;
697831 }
698832 }
699833
@@ -702,11 +836,43 @@ void ViveControllerManager::InputDevice::createPreferences() { · 2 unmodified lines
702836 auto preferences = DependencyManager::get<Preferences>();
703837 static const QString VIVE_PUCKS_CONFIG = "Vive Pucks Configuration";
704838
839+ {
840+ static const float MIN_VALUE = -3.0f;
841+ static const float MAX_VALUE = 3.0f;
842+ static const float STEP = 0.01f;
843+
844+ auto getter = [this]()->float { return HEAD_PUCK_Y_OFFSET; };
845+ auto setter = [this](const float& value) { HEAD_PUCK_Y_OFFSET = value; };
846+
847+ auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckYOffset", getter, setter);
848+ preference->setMin(MIN_VALUE);
849+ preference->setMax(MAX_VALUE);
850+ preference->setDecimals(3);
851+ preference->setStep(STEP);
852+ preferences->addPreference(preference);
853+ }
854+
855+ {
856+ static const float MIN_VALUE = -3.0f;
857+ static const float MAX_VALUE = 3.0f;
858+ static const float STEP = 0.01f;
859+
860+ auto getter = [this]()->float { return HEAD_PUCK_Z_OFFSET; };
861+ auto setter = [this](const float& value) { HEAD_PUCK_Z_OFFSET = value; };
862+
863+ auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckXOffset", getter, setter);
864+ preference->setMin(MIN_VALUE);
865+ preference->setMax(MAX_VALUE);
866+ preference->setStep(STEP);
867+ preference->setDecimals(3);
868+ preferences->addPreference(preference);
869+ }
870+
705871 {
706872 auto getter = [this]()->QString { return _configStringMap[_preferedConfig]; };
707873 auto setter = [this](const QString& value) { setConfigFromString(value); saveSettings(); };
708874 auto preference = new ComboBoxPreference(VIVE_PUCKS_CONFIG, "Configuration", getter, setter);
709− QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders"};
875+ QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders", "FeetHipsAndHead"};
710876 preference->setItems(list);
711877 preferences->addPreference(preference);
712878
plugins/openvr/src/ViveControllerManager.h+11−1
@@ -41,6 +41,8 @@ public: · 40 unmodified lines
4141 const QString getName() const override { return NAME; }
4242
4343 bool isHandController() const override { return true; }
44+ bool isHeadController() const override { return true; }
45+ bool isHeadControllerMounted() const;
4446
4547 bool activate() override;
4648 void deactivate() override;
@@ -54,6 +56,7 @@ private: · 7 unmodified lines
5456 class InputDevice : public controller::InputDevice {
5557 public:
5658 InputDevice(vr::IVRSystem*& system);
59+ bool isHeadControllerMounted() const { return _overrideHead; }
5760 private:
5861 // Device functions
5962 controller::Input::NamedVector getAvailableInputs() const override;
@@ -67,6 +70,7 @@ private: · 7 unmodified lines
6770 void calibrate(const controller::InputCalibrationData& inputCalibration);
6871 void uncalibrate();
6972 controller::Pose addOffsetToPuckPose(int joint) const;
73+ glm::mat4 recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration);
7074 void updateCalibratedLimbs();
7175 bool checkForCalibrationEvent();
7276 void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand);
@@ -84,11 +88,14 @@ private: · 11 unmodified lines
8488 void loadSettings();
8589 void saveSettings() const;
8690 void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
91+ void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition);
8792 void calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
8893 void calibrateChest(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
8994
9095 void calibrateShoulders(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration,
9196 int firstShoulderIndex, int secondShoulderIndex);
97+ void calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
98+
9299
93100 class FilteredStick {
94101 public:
@@ -119,6 +126,8 @@ private: · 24 unmodified lines
119126 FeetAndHips,
120127 FeetHipsAndChest,
121128 FeetHipsAndShoulders,
129+ FeetHipsChestAndHead,
130+ FeetHipsAndHead
122131 };
123132 Config _config { Config::Auto };
124133 Config _preferedConfig { Config::Auto };
@@ -137,7 +146,7 @@ private: · 12 unmodified lines
137146
138147 int _trackedControllers { 0 };
139148 vr::IVRSystem*& _system;
140− quint64 _timeTilCalibration { 0.0f };
149+ quint64 _timeTilCalibration { 0 };
141150 float _leftHapticStrength { 0.0f };
142151 float _leftHapticDuration { 0.0f };
143152 float _rightHapticStrength { 0.0f };
@@ -145,6 +154,7 @@ private: · 1 unmodified line
145154 bool _triggersPressedHandled { false };
146155 bool _calibrated { false };
147156 bool _timeTilCalibrationSet { false };
157+ bool _overrideHead { false };
148158 mutable std::recursive_mutex _lock;
149159
150160 QString configToString(Config config);
scripts/defaultScripts.js+2−1
@@ -31,7 +31,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ · 30 unmodified lines
3131 "system/tablet-ui/tabletUI.js"
3232 ];
3333 var DEFAULT_SCRIPTS_SEPARATE = [
34− "system/controllers/controllerScripts.js"
34+ "system/controllers/controllerScripts.js",
35+ "system/chat.js"
3536 ];
3637
3738 // add a menu item for debugging
scripts/developer/tests/avatarToWorldTests.js+127−0
@@ -0,0 +1,127 @@
1+var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}";
2+
3+var debugSphereBaseProperties = {
4+ type: "Sphere",
5+ dimensions: { x: 0.2, y: 0.2, z: 0.2 },
6+ dynamic: false,
7+ collisionless: true,
8+ gravity: { x: 0, y: 0, z: 0 },
9+ lifetime: 10.0,
10+ userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }"
11+};
12+
13+var debugBoxBaseProperties = {
14+ type: "Box",
15+ dimensions: { x: 0.2, y: 0.2, z: 0.2 },
16+ dynamic: false,
17+ collisionless: true,
18+ gravity: { x: 0, y: 0, z: 0 },
19+ lifetime: 10.0,
20+ userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }"
21+};
22+
23+//jointToWorldPoint
24+// create sphere for finger on left hand
25+// each frame, calculate world position of finger, with some offset
26+// update sphere to match this position
27+var jointToWorldPointTest_sphereEntity;
28+function jointToWorldPointTest() {
29+ var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4");
30+ var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 };
31+ var worldPos = MyAvatar.jointToWorldPoint(jointOffset_WorldSpace, jointIndex);
32+
33+ var jointSphereProps = Object.create(debugSphereBaseProperties);
34+ jointSphereProps.name = "jointToWorldPointTest_Sphere";
35+ jointSphereProps.color = { blue: 240, green: 150, red: 150 };
36+ jointSphereProps.position = worldPos;
37+ jointToWorldPointTest_sphereEntity = Entities.addEntity(jointSphereProps);
38+}
39+function jointToWorldPointTest_update(deltaTime) {
40+ var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4");
41+ var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 };
42+ var worldPos = MyAvatar.jointToWorldPoint(jointOffset_WorldSpace, jointIndex);
43+ var newProperties = { position: worldPos };
44+ Entities.editEntity(jointToWorldPointTest_sphereEntity, newProperties);
45+}
46+
47+//jointToWorldDirection
48+// create line in world space
49+// each frame calculate world space direction of players head z axis
50+// update line to match
51+var jointToWorldDirectionTest_lineEntity;
52+function jointToWorldDirectionTest() {
53+ var jointIndex = MyAvatar.getJointIndex("Head");
54+ var avatarPos = MyAvatar.getJointPosition(jointIndex);
55+
56+ var jointDir = { x: 1, y: 0, z: 1 };
57+ var worldDir = MyAvatar.jointToWorldDirection(jointDir, jointIndex);
58+ print(worldDir.x);
59+ print(worldDir.y);
60+ print(worldDir.z);
61+ jointToWorldDirectionTest_lineEntity = Entities.addEntity({
62+ type: "Line",
63+ color: {red: 250, green: 0, blue: 0},
64+ dimensions: {x: 5, y: 5, z: 5},
65+ lifetime: 10.0,
66+ linePoints: [{
67+ x: 0,
68+ y: 0,
69+ z: 0
70+ }, worldDir
71+ ],
72+ position : avatarPos,
73+ });
74+}
75+function jointToWorldDirection_update(deltaTime) {
76+ var jointIndex = MyAvatar.getJointIndex("Head");
77+ var avatarPos = MyAvatar.getJointPosition(jointIndex);
78+ var jointDir = { x: 1, y: 0, z: 0 };
79+ var worldDir = MyAvatar.jointToWorldDirection(jointDir, jointIndex);
80+ var newProperties = {
81+ linePoints: [{
82+ x: 0,
83+ y: 0,
84+ z: 0
85+ }, worldDir
86+ ],
87+ position : avatarPos
88+ };
89+
90+ Entities.editEntity(jointToWorldDirectionTest_lineEntity, newProperties);
91+}
92+
93+//jointToWorldRotation
94+// create box in world space
95+// each frame calculate world space rotation of players head
96+// update box rotation to match
97+var jointToWorldRotationTest_boxEntity;
98+function jointToWorldRotationTest() {
99+ var jointIndex = MyAvatar.getJointIndex("Head");
100+ var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex);
101+ var jointRot = MyAvatar.getJointRotation(jointIndex);
102+ var jointRot_WorldSpace = MyAvatar.jointToWorldRotation(jointRot, jointIndex);
103+
104+ var boxProps = Object.create(debugBoxBaseProperties);
105+ boxProps.name = "jointToWorldRotationTest_Box";
106+ boxProps.color = { blue: 250, green: 250, red: 250 };
107+ boxProps.position = jointPosition_WorldSpace;
108+ boxProps.rotation = jointRot_WorldSpace;
109+ jointToWorldRotationTest_boxEntity = Entities.addEntity(boxProps);
110+}
111+function jointToWorldRotationTest_update(deltaTime) {
112+ var jointIndex = MyAvatar.getJointIndex("Head");
113+ var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex);
114+ var jointRot = MyAvatar.getJointRotation(jointIndex);
115+ var jointRot_WorldSpace = MyAvatar.jointToWorldRotation(jointRot, jointIndex);
116+ var newProperties = { position: jointPosition_WorldSpace, rotation: jointRot_WorldSpace };
117+ Entities.editEntity(jointToWorldRotationTest_boxEntity, newProperties);
118+}
119+
120+jointToWorldPointTest();
121+Script.update.connect(jointToWorldPointTest_update);
122+
123+jointToWorldDirectionTest();
124+Script.update.connect(jointToWorldDirection_update);
125+
126+jointToWorldRotationTest();
127+Script.update.connect(jointToWorldRotationTest_update);
scripts/developer/tests/worldToAvatarTests.js+134−0
@@ -0,0 +1,134 @@
1+var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}";
2+
3+var debugSphereBaseProperties = {
4+ type: "Sphere",
5+ dimensions: { x: 0.2, y: 0.2, z: 0.2 },
6+ dynamic: false,
7+ collisionless: true,
8+ gravity: { x: 0, y: 0, z: 0 },
9+ lifetime: 10.0,
10+ userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }"
11+};
12+
13+var debugBoxBaseProperties = {
14+ type: "Box",
15+ dimensions: { x: 0.2, y: 0.2, z: 0.2 },
16+ dynamic: false,
17+ collisionless: true,
18+ gravity: { x: 0, y: 0, z: 0 },
19+ lifetime: 10.0,
20+ userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }"
21+};
22+
23+//worldToJointPoint
24+// calculate position offset from joint using getJointPosition
25+// pass through worldToJointPoint to get offset in joint space of players joint
26+// create a blue sphere and attach it to players joint using the joint space offset
27+// The two spheres should appear in the same place, but the blue sphere will follow the avatar
28+function worldToJointPointTest() {
29+ var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4");
30+ var avatarPos = MyAvatar.position;
31+
32+ var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex);
33+ var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 };
34+ var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace);
35+ var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex);
36+
37+ var jointSphereProps = Object.create(debugSphereBaseProperties);
38+ jointSphereProps.name = "worldToJointPointTest_Joint";
39+ jointSphereProps.color = { blue: 240, green: 150, red: 150 };
40+ jointSphereProps.localPosition = jointPosition_JointSpaceOffset;
41+ jointSphereProps.parentID = AVATAR_SELF_ID;
42+ jointSphereProps.parentJointIndex = jointIndex;
43+ Entities.addEntity(jointSphereProps);
44+
45+ var worldSphereProps = Object.create(debugSphereBaseProperties);
46+ worldSphereProps.name = "worldToJointPointTest_World";
47+ worldSphereProps.color = { blue: 150, green: 250, red: 150 };
48+ worldSphereProps.position = jointPosition_WorldSpaceOffset;
49+ Entities.addEntity(worldSphereProps);
50+}
51+
52+//worldToJointDirection
53+// create line and attach to avatars head
54+// each frame calculate direction of world x axis in joint space of players head
55+// update arrow orientation to match
56+var worldToJointDirectionTest_lineEntity;
57+function worldToJointDirectionTest() {
58+ var jointIndex = MyAvatar.getJointIndex("Head");
59+
60+ var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex);
61+ var jointOffset_WorldSpace = { x: 0, y: 0, z: 0 };
62+ var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace);
63+ var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex);
64+
65+ var worldDir = { x: 1, y: 0, z: 0 };
66+ var avatarDir = MyAvatar.worldToJointDirection(worldDir, jointIndex);
67+
68+ worldToJointDirectionTest_lineEntity = Entities.addEntity({
69+ type: "Line",
70+ color: {red: 200, green: 250, blue: 0},
71+ dimensions: {x: 5, y: 5, z: 5},
72+ lifetime: 10.0,
73+ linePoints: [{
74+ x: 0,
75+ y: 0,
76+ z: 0
77+ }, avatarDir
78+ ],
79+ localPosition : jointOffset_WorldSpace,
80+ parentID : AVATAR_SELF_ID,
81+ parentJointIndex : jointIndex
82+ });
83+}
84+
85+function worldToJointDirectionTest_update(deltaTime) {
86+ var jointIndex = MyAvatar.getJointIndex("Head");
87+ var worldDir = { x: 1, y: 0, z: 0 };
88+ var avatarDir = MyAvatar.worldToJointDirection(worldDir, jointIndex);
89+ var newProperties = { linePoints: [{
90+ x: 0,
91+ y: 0,
92+ z: 0
93+ }, avatarDir
94+ ]};
95+
96+ Entities.editEntity(worldToJointDirectionTest_lineEntity, newProperties);
97+}
98+
99+//worldToJointRotation
100+// create box and parent to some player joint
101+// convert world identity rotation to joint space rotation
102+// each frame, update box with new orientation
103+var worldToJointRotationTest_boxEntity;
104+function worldToJointRotationTest() {
105+ var jointIndex = MyAvatar.getJointIndex("RightHandPinky4");
106+ var avatarPos = MyAvatar.position;
107+
108+ var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex);
109+ var jointOffset_WorldSpace = { x: 0.0, y: 0, z: 0 };
110+ var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace);
111+ var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex);
112+
113+ var jointBoxProps = Object.create(debugBoxBaseProperties);
114+ jointBoxProps.name = "worldToJointRotationTest_Box";
115+ jointBoxProps.color = { blue: 0, green: 0, red: 250 };
116+ jointBoxProps.localPosition = jointPosition_JointSpaceOffset;
117+ jointBoxProps.parentID = AVATAR_SELF_ID;
118+ jointBoxProps.parentJointIndex = jointIndex;
119+ worldToJointRotationTest_boxEntity = Entities.addEntity(jointBoxProps);
120+}
121+function worldToJointRotationTest_update(deltaTime) {
122+ var jointIndex = MyAvatar.getJointIndex("RightHandPinky4");
123+ var worldRot = Quat.fromPitchYawRollDegrees(0,0,0);
124+ var avatarRot = MyAvatar.worldToJointRotation(worldRot, jointIndex);
125+ var newProperties = { localRotation: avatarRot };
126+ Entities.editEntity(worldToJointRotationTest_boxEntity, newProperties);
127+}
128+
129+worldToJointPointTest();
130+worldToJointDirectionTest();
131+worldToJointRotationTest();
132+
133+Script.update.connect(worldToJointDirectionTest_update);
134+Script.update.connect(worldToJointRotationTest_update);
scripts/developer/utilities/render/deferredLighting.qml+16−6
@@ -64,6 +64,7 @@ Column { · 63 unmodified lines
6464 "Point:LightingModel:enablePointLight",
6565 "Spot:LightingModel:enableSpotLight",
6666 "Light Contour:LightingModel:showLightContour",
67+ "Zone Stack:DrawZoneStack:enabled",
6768 "Shadow:RenderShadowTask:enabled"
6869 ]
6970 CheckBox {
@@ -162,13 +163,9 @@ Column { · 92 unmodified lines
162163 }
163164 }
164165
166+ Row {
165167 Column {
166− id: metas
167− CheckBox {
168− text: "Metas"
169− checked: Render.getConfig("DrawMetaBounds")["enabled"]
170− onCheckedChanged: { Render.getConfig("DrawMetaBounds")["enabled"] = checked }
171− }
168+
172169 CheckBox {
173170 text: "Opaques"
174171 checked: Render.getConfig("DrawOpaqueBounds")["enabled"]
@@ -189,11 +186,24 @@ Column { · 14 unmodified lines
189186 checked: Render.getConfig("DrawOverlayTransparentBounds")["enabled"]
190187 onCheckedChanged: { Render.getConfig("DrawOverlayTransparentBounds")["enabled"] = checked }
191188 }
189+ }
190+ Column {
191+ CheckBox {
192+ text: "Metas"
193+ checked: Render.getConfig("DrawMetaBounds")["enabled"]
194+ onCheckedChanged: { Render.getConfig("DrawMetaBounds")["enabled"] = checked }
195+ }
196+ CheckBox {
197+ text: "Lights"
198+ checked: Render.getConfig("DrawLightBounds")["enabled"]
199+ onCheckedChanged: { Render.getConfig("DrawLightBounds")["enabled"] = checked; }
200+ }
192201 CheckBox {
193202 text: "Zones"
194203 checked: Render.getConfig("DrawZones")["enabled"]
195204 onCheckedChanged: { Render.getConfig("ZoneRenderer")["enabled"] = checked; Render.getConfig("DrawZones")["enabled"] = checked; }
196205 }
197206 }
207+ }
198208 }
199209
scripts/developer/utilities/render/stats.qml+6−7
@@ -20,9 +20,9 @@ Item { · 19 unmodified lines
2020 id: stats
2121 spacing: 8
2222 anchors.fill:parent
23−
23+
2424 property var config: Render.getConfig("Stats")
25−
25+
2626 function evalEvenHeight() {
2727 // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ?
2828 return (height - spacing * (children.length - 1)) / children.length
@@ -81,7 +81,7 @@ Item { · 52 unmodified lines
8181 color: "#1AC567"
8282 },
8383 {
84− prop: "textureGPUTransferCount",
84+ prop: "texturePendingGPUTransferCount",
8585 label: "Transfer",
8686 color: "#9495FF"
8787 }
@@ -158,7 +158,7 @@ Item { · 70 unmodified lines
158158 }
159159 ]
160160 }
161−
161+
162162 PlotPerf {
163163 title: "State Changes"
164164 height: parent.evalEvenHeight()
@@ -180,7 +180,7 @@ Item { · 15 unmodified lines
180180 color: "#1AC567"
181181 }
182182 ]
183− }
183+ }
184184
185185 property var drawOpaqueConfig: Render.getConfig("DrawOpaqueDeferred")
186186 property var drawTransparentConfig: Render.getConfig("DrawTransparentDeferred")
@@ -211,7 +211,7 @@ Item { · 24 unmodified lines
211211 color: "#FED959"
212212 }
213213 ]
214− }
214+ }
215215
216216 PlotPerf {
217217 title: "Timing"
@@ -250,4 +250,3 @@ Item { · 32 unmodified lines
250250 }
251251
252252 }
253−
scripts/system/assets/sounds/snap.wav+0−0

Binary file not shown.

scripts/system/assets/sounds/sound-print-photo.wav+0−0

Binary file not shown.

scripts/system/bubble.js+11−6
@@ -10,7 +10,7 @@ · 9 unmodified lines
1010 // Distributed under the Apache License, Version 2.0.
1111 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
1212 //
13−/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */
13+/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation, UserActivityLogger */
1414
1515 (function () { // BEGIN LOCAL_SCOPE
1616 var button;
@@ -76,6 +76,7 @@ · 59 unmodified lines
7676 // Called from the C++ scripting interface to show the bubble overlay
7777 function enteredIgnoreRadius() {
7878 createOverlays();
79+ UserActivityLogger.bubbleActivated();
7980 }
8081
8182 // Used to set the state of the bubble HUD button
@@ -139,10 +140,14 @@ · 57 unmodified lines
139140 }
140141
141142 // When the space bubble is toggled...
142− function onBubbleToggled() {
143− var bubbleActive = Users.getIgnoreRadiusEnabled();
144− writeButtonProperties(bubbleActive);
145− if (bubbleActive) {
143+ // NOTE: the c++ calls this with just the first param -- we added a second
144+ // just for not logging the initial state of the bubble when we startup.
145+ function onBubbleToggled(enabled, doNotLog) {
146+ writeButtonProperties(enabled);
147+ if (doNotLog !== true) {
148+ UserActivityLogger.bubbleToggled(enabled);
149+ }
150+ if (enabled) {
146151 createOverlays();
147152 } else {
148153 hideOverlays();
@@ -163,7 +168,7 @@ · 14 unmodified lines
163168 sortOrder: 4
164169 });
165170
166− onBubbleToggled();
171+ onBubbleToggled(Users.getIgnoreRadiusEnabled(), true); // pass in true so we don't log this initial one in the UserActivity table
167172
168173 button.clicked.connect(Users.toggleIgnoreRadius);
169174 Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled);
scripts/system/chat.jsrenamed from unpublishedScripts/marketplace/chat/Chat.js+1−1
@@ -9,7 +9,7 @@ · 8 unmodified lines
99
1010 (function() {
1111
12− var webPageURL = "ChatPage.html"; // URL of tablet web page.
12+ var webPageURL = Script.resolvePath("html/ChatPage.html"); // URL of tablet web page.
1313 var randomizeWebPageURL = true; // Set to true for debugging.
1414 var lastWebPageURL = ""; // Last random URL of tablet web page.
1515 var onChatPage = false; // True when chat web page is opened.
scripts/system/controllers/handControllerGrab.js+2−1
@@ -3334,7 +3334,8 @@ function MyController(hand) { · 3333 unmodified lines
33343334 // If both secondary triggers squeezed, and the non-holding hand is empty, start scaling
33353335 if (this.secondarySqueezed() &&
33363336 this.getOtherHandController().secondarySqueezed() &&
3337− this.getOtherHandController().state === STATE_OFF) {
3337+ this.grabbedThingID && this.getOtherHandController().grabbedThingID &&
3338+ this.grabbedThingID == this.getOtherHandController().grabbedThingID) {
33383339 this.scalingStartDistance = Vec3.length(Vec3.subtract(this.getHandPosition(),
33393340 this.getOtherHandController().getHandPosition()));
33403341 this.scalingStartDimensions = props.dimensions;
scripts/system/controllers/teleport.js+17−37
@@ -366,45 +366,25 @@ function Teleporter() { · 365 unmodified lines
366366 }
367367
368368 // related to repositioning the avatar after you teleport
369+var FOOT_JOINT_NAMES = ["RightToe_End", "RightToeBase", "RightFoot"];
370+var DEFAULT_ROOT_TO_FOOT_OFFSET = 0.5;
369371 function getAvatarFootOffset() {
370− var data = getJointData();
371− var upperLeg, lowerLeg, foot, toe, toeTop;
372− data.forEach(function(d) {
373−
374− var jointName = d.joint;
375− if (jointName === "RightUpLeg") {
376− upperLeg = d.translation.y;
377− } else if (jointName === "RightLeg") {
378− lowerLeg = d.translation.y;
379− } else if (jointName === "RightFoot") {
380− foot = d.translation.y;
381− } else if (jointName === "RightToeBase") {
382− toe = d.translation.y;
383− } else if (jointName === "RightToe_End") {
384− toeTop = d.translation.y;
385− }
386− });
387−
388− var offset = upperLeg + lowerLeg + foot + toe + toeTop;
389− offset = offset / 100;
390− return offset;
391−}
392372
393−function getJointData() {
394− var allJointData = [];
395− var jointNames = MyAvatar.jointNames;
396− jointNames.forEach(function(joint, index) {
397− var translation = MyAvatar.getJointTranslation(index);
398− var rotation = MyAvatar.getJointRotation(index);
399− allJointData.push({
400− joint: joint,
401− index: index,
402− translation: translation,
403− rotation: rotation
404− });
405− });
406−
407− return allJointData;
373+ // find a valid foot jointIndex
374+ var footJointIndex = -1;
375+ var i, l = FOOT_JOINT_NAMES.length;
376+ for (i = 0; i < l; i++) {
377+ footJointIndex = MyAvatar.getJointIndex(FOOT_JOINT_NAMES[i]);
378+ if (footJointIndex != -1) {
379+ break;
380+ }
381+ }
382+ if (footJointIndex != -1) {
383+ // default vertical offset from foot to avatar root.
384+ return -MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(footJointIndex).y;
385+ } else {
386+ return DEFAULT_ROOT_TO_FOOT_OFFSET * MyAvatar.scale;
387+ }
408388 }
409389
410390 var leftPad = new ThumbPad('left');
scripts/system/edit.js+173−63
@@ -12,7 +12,7 @@ · 11 unmodified lines
1212 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
1313 //
1414
15−/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger,
15+/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger,
1616 Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */
1717
1818 (function() { // BEGIN LOCAL_SCOPE
@@ -80,27 +80,7 @@ selectionManager.addEventListener(function () { · 61 unmodified lines
8080 }
8181 var type = Entities.getEntityProperties(selectedEntityID, "type").type;
8282 if (type === "ParticleEffect") {
83− // Destroy the old particles web view first
84− particleExplorerTool.destroyWebView();
85− particleExplorerTool.createWebView();
86− var properties = Entities.getEntityProperties(selectedEntityID);
87− var particleData = {
88− messageType: "particle_settings",
89− currentProperties: properties
90− };
91− selectedParticleEntityID = selectedEntityID;
92− particleExplorerTool.setActiveParticleEntity(selectedParticleEntityID);
93−
94− particleExplorerTool.webView.webEventReceived.connect(function (data) {
95− data = JSON.parse(data);
96− if (data.messageType === "page_loaded") {
97− particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
98− }
99− });
100−
101− // Switch to particle explorer
102− var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
103− tablet.sendToQml({method: 'selectTab', params: {id: 'particle'}});
83+ selectParticleEntity(selectedEntityID);
10484 } else {
10585 needToDestroyParticleExplorer = true;
10686 }
@@ -217,6 +197,32 @@ function hideMarketplace() { · 110 unmodified lines
217197 // }
218198 // }
219199
200+function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) {
201+ // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original
202+ // position in the given direction.
203+ var CORNERS = [
204+ { x: 0, y: 0, z: 0 },
205+ { x: 0, y: 0, z: 1 },
206+ { x: 0, y: 1, z: 0 },
207+ { x: 0, y: 1, z: 1 },
208+ { x: 1, y: 0, z: 0 },
209+ { x: 1, y: 0, z: 1 },
210+ { x: 1, y: 1, z: 0 },
211+ { x: 1, y: 1, z: 1 },
212+ ];
213+
214+ // Go through all corners and find least (most negative) distance in front of position.
215+ var distance = 0;
216+ for (var i = 0, length = CORNERS.length; i < length; i++) {
217+ var cornerVector =
218+ Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions));
219+ var cornerDistance = Vec3.dot(cornerVector, direction);
220+ distance = Math.min(cornerDistance, distance);
221+ }
222+ position = Vec3.sum(Vec3.multiply(distance, direction), position);
223+ return position;
224+}
225+
220226 var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
221227 var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit";
222228 var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable";
@@ -234,6 +240,32 @@ var toolBar = (function () { · 11 unmodified lines
234240 var position = getPositionToCreateEntity();
235241 var entityID = null;
236242 if (position !== null && position !== undefined) {
243+ var direction;
244+ if (Camera.mode === "entity" || Camera.mode === "independent") {
245+ direction = Camera.orientation;
246+ } else {
247+ direction = MyAvatar.orientation;
248+ }
249+ direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z);
250+
251+ var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"];
252+ if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) {
253+ // Adjust position of entity per bounding box prior to creating it.
254+ var registration = properties.registration;
255+ if (registration === undefined) {
256+ var DEFAULT_REGISTRATION = { x: 0.5, y: 0.5, z: 0.5 };
257+ registration = DEFAULT_REGISTRATION;
258+ }
259+
260+ var orientation = properties.orientation;
261+ if (orientation === undefined) {
262+ var DEFAULT_ORIENTATION = Quat.fromPitchYawRollDegrees(0, 0, 0);
263+ orientation = DEFAULT_ORIENTATION;
264+ }
265+
266+ position = adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation);
267+ }
268+
237269 position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions);
238270 properties.position = position;
239271 if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)) {
@@ -243,6 +275,32 @@ var toolBar = (function () { · 3 unmodified lines
243275 if (properties.type == "ParticleEffect") {
244276 selectParticleEntity(entityID);
245277 }
278+
279+ var POST_ADJUST_ENTITY_TYPES = ["Model"];
280+ if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) {
281+ // Adjust position of entity per bounding box after it has been created and auto-resized.
282+ var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions;
283+ var DIMENSIONS_CHECK_INTERVAL = 200;
284+ var MAX_DIMENSIONS_CHECKS = 10;
285+ var dimensionsCheckCount = 0;
286+ var dimensionsCheckFunction = function () {
287+ dimensionsCheckCount++;
288+ var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]);
289+ if (!Vec3.equal(properties.dimensions, initialDimensions)) {
290+ position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint,
291+ properties.dimensions, properties.rotation);
292+ position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions),
293+ properties.dimensions);
294+ Entities.editEntity(entityID, {
295+ position: position
296+ });
297+ selectionManager._update();
298+ } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) {
299+ Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL);
300+ }
301+ };
302+ Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL);
303+ }
246304 } else {
247305 Window.notifyEditError("Can't create " + properties.type + ": " +
248306 properties.type + " would be out of bounds.");
@@ -540,7 +598,6 @@ var toolBar = (function () { · 291 unmodified lines
540598
541599 that.toggle = function () {
542600 that.setActive(!isActive);
543− activeButton.editProperties({isActive: isActive});
544601 if (!isActive) {
545602 tablet.gotoHomeScreen();
546603 }
@@ -564,6 +621,10 @@ var toolBar = (function () { · 17 unmodified lines
564621 enabled: active
565622 }));
566623 isActive = active;
624+ activeButton.editProperties({isActive: isActive});
625+
626+ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
627+
567628 if (!isActive) {
568629 entityListTool.setVisible(false);
569630 gridTool.setVisible(false);
@@ -572,8 +633,8 @@ var toolBar = (function () { · 2 unmodified lines
572633 selectionManager.clearSelections();
573634 cameraManager.disable();
574635 selectionDisplay.triggerMapping.disable();
636+ tablet.landscape = false;
575637 } else {
576− var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
577638 tablet.loadQMLSource("Edit.qml");
578639 UserActivityLogger.enabledEdit();
579640 entityListTool.setVisible(true);
@@ -581,6 +642,8 @@ var toolBar = (function () { · 1 unmodified line
581642 grid.setEnabled(true);
582643 propertiesTool.setVisible(true);
583644 selectionDisplay.triggerMapping.enable();
645+ print("starting tablet in landscape mode")
646+ tablet.landscape = true;
584647 // Not sure what the following was meant to accomplish, but it currently causes
585648 // everybody else to think that Interface has lost focus overall. fogbugzid:558
586649 // Window.setFocus();
@@ -1290,7 +1353,7 @@ function parentSelectedEntities() { · 703 unmodified lines
12901353 }
12911354 });
12921355
1293− if(parentCheck) {
1356+ if (parentCheck) {
12941357 Window.notify("Entities parented");
12951358 }else {
12961359 Window.notify("Entities are already parented to last");
@@ -1407,40 +1470,26 @@ function handeMenuEvent(menuItem) { · 110 unmodified lines
14071470 }
14081471 tooltip.show(false);
14091472 }
1410−function getPositionToCreateEntity() {
1411− var HALF_TREE_SCALE = 16384;
1412− var direction = Quat.getForward(MyAvatar.orientation);
1413− var distance = 1;
1414− var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, distance));
1415−
1416− if (Camera.mode === "entity" || Camera.mode === "independent") {
1417− position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), distance));
1418− }
1419− position.y += 0.5;
1420− if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
1421− return null;
1422− }
1423− return position;
1424−}
14251473
1426−function getPositionToImportEntity() {
1427− var dimensions = Clipboard.getContentsDimensions();
1428− var HALF_TREE_SCALE = 16384;
1429− var direction = Quat.getForward(MyAvatar.orientation);
1430− var longest = 1;
1431− longest = Math.sqrt(Math.pow(dimensions.x, 2) + Math.pow(dimensions.z, 2));
1432− var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, longest));
1474+var HALF_TREE_SCALE = 16384;
14331475
1476+function getPositionToCreateEntity(extra) {
1477+ var CREATE_DISTANCE = 2;
1478+ var position;
1479+ var delta = extra !== undefined ? extra : 0;
14341480 if (Camera.mode === "entity" || Camera.mode === "independent") {
1435− position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), longest));
1481+ position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta));
1482+ } else {
1483+ position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta));
1484+ position.y += 0.5;
14361485 }
14371486
14381487 if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
14391488 return null;
14401489 }
1441−
14421490 return position;
14431491 }
1492+
14441493 function importSVO(importURL) {
14451494 if (!Entities.canRez() && !Entities.canRezTmp()) {
14461495 Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG);
@@ -1458,22 +1507,73 @@ function importSVO(importURL) { · 11 unmodified lines
14581507
14591508 if (success) {
14601509 var VERY_LARGE = 10000;
1461− var position = {
1462− x: 0,
1463− y: 0,
1464− z: 0
1465− };
1466− if (Clipboard.getClipboardContentsLargestDimension() < VERY_LARGE) {
1467− position = getPositionToImportEntity();
1510+ var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE;
1511+ var position = Vec3.ZERO;
1512+ if (!isLargeImport) {
1513+ position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2);
14681514 }
14691515 if (position !== null && position !== undefined) {
14701516 var pastedEntityIDs = Clipboard.pasteEntities(position);
1517+ if (!isLargeImport) {
1518+ // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move
1519+ // entities after they're imported so that they're all the correct distance in front of and with geometric mean
1520+ // centered on the avatar/camera direction.
1521+ var deltaPosition = Vec3.ZERO;
1522+
1523+ var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]);
1524+ var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"];
1525+ if (NO_ADJUST_ENTITY_TYPES.indexOf(properties.type) === -1) {
1526+ var targetDirection;
1527+ if (Camera.mode === "entity" || Camera.mode === "independent") {
1528+ targetDirection = Camera.orientation;
1529+ } else {
1530+ targetDirection = MyAvatar.orientation;
1531+ }
1532+ targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z);
1533+
1534+ var targetPosition = getPositionToCreateEntity();
1535+ var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection.
1536+ var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection.
1537+ var entityPositions = [];
1538+ for (var i = 0, length = pastedEntityIDs.length; i < length; i++) {
1539+ var properties = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions",
1540+ "registrationPoint", "rotation"]);
1541+ var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection,
1542+ properties.registrationPoint, properties.dimensions, properties.rotation);
1543+ var delta = Vec3.subtract(adjustedPosition, properties.position);
1544+ var distance = Vec3.dot(delta, targetDirection);
1545+ deltaParallel = Math.min(distance, deltaParallel);
1546+ deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)),
1547+ deltaPerpendicular);
1548+ entityPositions[i] = properties.position;
1549+ }
1550+ deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular);
1551+ deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular);
1552+ }
1553+
1554+ if (grid.getSnapToGrid()) {
1555+ var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions",
1556+ "registrationPoint"]);
1557+ var position = Vec3.sum(deltaPosition, properties.position);
1558+ position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions,
1559+ properties.registrationPoint), properties.dimensions, properties.registrationPoint);
1560+ deltaPosition = Vec3.subtract(position, properties.position);
1561+ }
1562+
1563+ if (!Vec3.equal(deltaPosition, Vec3.ZERO)) {
1564+ for (var i = 0, length = pastedEntityIDs.length; i < length; i++) {
1565+ Entities.editEntity(pastedEntityIDs[i], {
1566+ position: Vec3.sum(deltaPosition, entityPositions[i])
1567+ });
1568+ }
1569+ }
1570+ }
14711571
14721572 if (isActive) {
14731573 selectionManager.setSelections(pastedEntityIDs);
14741574 }
14751575 } else {
1476− Window.notifyEditError("Can't import objects: objects would be out of bounds.");
1576+ Window.notifyEditError("Can't import entities: entities would be out of bounds.");
14771577 }
14781578 } else {
14791579 Window.notifyEditError("There was an error importing the entity file.");
@@ -1787,11 +1887,11 @@ var PropertiesTool = function (opts) { · 307 unmodified lines
17871887 }
17881888 pushCommandForSelections();
17891889 selectionManager._update();
1790− } else if(data.type === 'parent') {
1890+ } else if (data.type === 'parent') {
17911891 parentSelectedEntities();
1792− } else if(data.type === 'unparent') {
1892+ } else if (data.type === 'unparent') {
17931893 unparentSelectedEntities();
1794− } else if(data.type === 'saveUserData'){
1894+ } else if (data.type === 'saveUserData'){
17951895 //the event bridge and json parsing handle our avatar id string differently.
17961896 var actualID = data.id.split('"')[1];
17971897 Entities.editEntity(actualID, data.properties);
@@ -2083,6 +2183,10 @@ var selectedParticleEntityID = null; · 285 unmodified lines
20832183
20842184 function selectParticleEntity(entityID) {
20852185 var properties = Entities.getEntityProperties(entityID);
2186+
2187+ if (properties.emitOrientation) {
2188+ properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation);
2189+ }
20862190 var particleData = {
20872191 messageType: "particle_settings",
20882192 currentProperties: properties
@@ -2092,6 +2196,7 @@ function selectParticleEntity(entityID) { · 3 unmodified lines
20922196
20932197 selectedParticleEntity = entityID;
20942198 particleExplorerTool.setActiveParticleEntity(entityID);
2199+
20952200 particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
20962201
20972202 // Switch to particle explorer
@@ -2100,10 +2205,16 @@ function selectParticleEntity(entityID) { · 2 unmodified lines
21002205 }
21012206
21022207 entityListTool.webView.webEventReceived.connect(function (data) {
2103− data = JSON.parse(data);
2208+ try {
2209+ data = JSON.parse(data);
2210+ } catch(e) {
2211+ print("edit.js: Error parsing JSON: " + e.name + " data " + data)
2212+ return;
2213+ }
2214+
21042215 if (data.type === 'parent') {
21052216 parentSelectedEntities();
2106− } else if(data.type === 'unparent') {
2217+ } else if (data.type === 'unparent') {
21072218 unparentSelectedEntities();
21082219 } else if (data.type === "selectionUpdate") {
21092220 var ids = data.entityIds;
@@ -2124,4 +2235,3 @@ entityListTool.webView.webEventReceived.connect(function (data) { · 14 unmodified lines
21242235 });
21252236
21262237 }()); // END LOCAL_SCOPE
2127−
scripts/system/html/ChatPage.htmlrenamed from unpublishedScripts/marketplace/chat/ChatPage.html+0−0

File renamed without changes.

scripts/system/html/SnapshotReview.html+5−1
@@ -28,7 +28,11 @@ · 27 unmodified lines
2828 </form>
2929 </div>
3030 <input type="button" id="snap-button" onclick="takeSnapshot()" />
31− <div id="snap-settings-right"></div>
31+ <div id="snap-settings-right">
32+ <button type="image" class="blueButton" id="print-button" onclick="printToPolaroid()">
33+ <div id="print-icon" class="print-icon print-icon-default">
34+ </button>
35+ </div>
3236 </div>
3337 </body>
3438 </html>
scripts/system/html/css/SnapshotReview.css+37−0
@@ -286,6 +286,43 @@ input[type=button].naked:active { · 285 unmodified lines
286286 // END styling of snapshot controls (bottom panel) and its contents
287287 */
288288
289+
290+/*
291+// START polaroid styling
292+*/
293+
294+#print-button {
295+ width: 72px;
296+ height: 72px;
297+ margin-left: 30px;
298+ margin-top: -10px;
299+ box-sizing: content-box;
300+ display: inline;
301+ outline:none;
302+}
303+
304+.print-icon {
305+ margin: auto;
306+}
307+
308+.print-icon-default {
309+ background: url(../img/button-snap-print.svg) no-repeat;
310+ margin-right: -1px;
311+ width: 64px;
312+ height: 64px;
313+}
314+
315+.print-icon-loading {
316+ background: url(../img/loader.gif) no-repeat;
317+ width: 32px;
318+ height: 32px;
319+}
320+
321+/*
322+// END polaroid styling
323+*/
324+
325+
289326 /*
290327 // START misc styling
291328 */
scripts/system/html/css/edit-style.css+3−1
@@ -610,6 +610,7 @@ hr { · 609 unmodified lines
610610 .dropdown dl[dropped="true"] {
611611 color: #404040;
612612 background: linear-gradient(#afafaf, #afafaf);
613+ z-index: 998;
613614 }
614615
615616 .dropdown dt {
@@ -657,7 +658,8 @@ hr { · 41 unmodified lines
657658 font-family: FiraSans-SemiBold;
658659 font-size: 15px;
659660 color: #404040;
660− background-color: #afafaf
661+ background-color: #afafaf;
662+ z-index: 999;
661663 }
662664 .dropdown li:hover {
663665 background-color: #00b4ef;
scripts/system/html/css/hifi-style.css+1−1
@@ -172,4 +172,4 @@ input[type=radio]:active + label > span > span{ · 171 unmodified lines
172172 }
173173 .blueButton:disabled {
174174 background-image: linear-gradient(#FFFFFF, #AFAFAF);
175−}
175+}
scripts/system/html/css/jsoneditor.css+1−1
@@ -503,7 +503,7 @@ div.jsoneditor-contextmenu-root { · 502 unmodified lines
503503 div.jsoneditor-contextmenu {
504504 position: absolute;
505505 box-sizing: content-box;
506− z-index: 99999;
506+ z-index: 998;
507507 }
508508
509509 div.jsoneditor-contextmenu ul,
scripts/system/html/entityProperties.html+3−0
@@ -51,6 +51,9 @@ · 50 unmodified lines
5151 <option value="Hexagon">Hexagon</option>
5252 <option value="Triangle">Triangle</option>
5353 <option value="Octagon">Octagon</option>
54+ <option value="Cylinder">Cylinder</option>
55+ <option value="Cone">Cone</option>
56+ <option value="Circle">Circle</option>
5457 </select>
5558 </div>
5659 <div class="property text">
scripts/system/html/img/button-snap-print.svg+25−0
@@ -0,0 +1,25 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4+ viewBox="0 0 199.8 231" style="enable-background:new 0 0 199.8 231;" xml:space="preserve">
5+<style type="text/css">
6+ .st0{fill:#FFFFFF;}
7+</style>
8+<g>
9+ <g>
10+ <path class="st0" d="M146.7,145.3H48V50.1h98.8V145.3z M56.2,122.8h83.1V57.6H56.2V122.8z"/>
11+ <circle class="st0" cx="96.8" cy="83.5" r="10.9"/>
12+ <path class="st0" d="M116.9,112.8h-40v-1.2c0-6.5,5.9-11.8,12.5-11.8H105c6.5,0,11.8,5.3,11.8,11.8V112.8z"/>
13+ <polygon class="st0" points="155.5,80.6 155.5,125.7 170.9,39.2 74.6,19.6 70.3,43.7 78.3,43.7 81,28.8 161.7,45.6 "/>
14+ </g>
15+ <g>
16+ <path class="st0" d="M12.4,213.7v-29.8h14c6.5,0,10,4.4,10,9.6c0,5.2-3.6,9.6-10,9.6h-7.6v10.6H12.4z M29.9,193.4
17+ c0-2.5-1.9-4-4.4-4h-6.7v8h6.7C28,197.4,29.9,195.9,29.9,193.4z"/>
18+ <path class="st0" d="M69.9,213.7L64,203.1h-4.7v10.6H53v-29.8h14c6.2,0,10.1,4.1,10.1,9.6c0,5.2-3.4,8.1-6.6,8.9l6.8,11.4H69.9z
19+ M70.5,193.4c0-2.5-1.9-4-4.4-4h-6.7v8.1h6.7C68.6,197.5,70.5,195.9,70.5,193.4z"/>
20+ <path class="st0" d="M94.4,213.7v-29.8h6.4v29.8H94.4z"/>
21+ <path class="st0" d="M139.8,213.7l-14.2-19.5v19.5h-6.4v-29.8h6.5l13.8,18.7v-18.7h6.4v29.8H139.8z"/>
22+ <path class="st0" d="M171.4,213.7v-24.2h-8.7v-5.6h23.8v5.6h-8.7v24.2H171.4z"/>
23+ </g>
24+</g>
25+</svg>
scripts/system/html/js/SnapshotReview.js+101−20
@@ -21,6 +21,11 @@ var blastShareText = "Blast to my Connections", · 20 unmodified lines
2121 hifiAlreadySharedText = "Already Shared to Snaps Feed",
2222 facebookShareText = "Share to Facebook",
2323 twitterShareText = "Share to Twitter";
24+
25+function fileExtensionMatches(filePath, extension) {
26+ return filePath.split('.').pop().toLowerCase() === extension;
27+}
28+
2429 function showSetupInstructions() {
2530 var snapshotImagesDiv = document.getElementById("snapshot-images");
2631 snapshotImagesDiv.className = "snapshotInstructions";
@@ -276,10 +281,10 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi · 249 unmodified lines
276281 if (!image_data.localPath) {
277282 return;
278283 }
279− var id = "p" + (idCounter++),
280− imageContainer = document.createElement("DIV"),
284+ var imageContainer = document.createElement("DIV"),
281285 img = document.createElement("IMG"),
282− isGif;
286+ isGif = fileExtensionMatches(image_data.localPath, "gif"),
287+ id = "p" + (isGif ? "1" : "0");
283288 imageContainer.id = id;
284289 imageContainer.style.width = "95%";
285290 imageContainer.style.height = "240px";
@@ -290,22 +295,34 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi · 4 unmodified lines
290295 imageContainer.style.position = "relative";
291296 img.id = id + "img";
292297 img.src = image_data.localPath;
293− isGif = img.src.split('.').pop().toLowerCase() === "gif";
294298 imageContainer.appendChild(img);
295299 document.getElementById("snapshot-images").appendChild(imageContainer);
296300 paths.push(image_data.localPath);
297− if (isGif) {
298− imageContainer.innerHTML += '<span class="gifLabel">GIF</span>';
299− }
300− if (!isGifLoading) {
301− appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast);
302− }
303− if (!isGifLoading || (isShowingPreviousImages && !image_data.story_id)) {
304− shareForUrl(id);
305− }
306− if (isShowingPreviousImages && isLoggedIn && image_data.story_id) {
307− updateShareInfo(id, image_data.story_id);
308− }
301+ img.onload = function () {
302+ if (isGif) {
303+ imageContainer.innerHTML += '<span class="gifLabel">GIF</span>';
304+ }
305+ if (!isGifLoading) {
306+ appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast);
307+ }
308+ if ((!isShowingPreviousImages && ((isGif && !isGifLoading) || !isGif)) || (isShowingPreviousImages && !image_data.story_id)) {
309+ shareForUrl(id);
310+ }
311+ if (isShowingPreviousImages && isLoggedIn && image_data.story_id) {
312+ updateShareInfo(id, image_data.story_id);
313+ }
314+ if (isShowingPreviousImages) {
315+ requestPrintButtonUpdate();
316+ }
317+ };
318+ img.onerror = function () {
319+ img.onload = null;
320+ img.src = image_data.errorPath;
321+ EventBridge.emitWebEvent(JSON.stringify({
322+ type: "snapshot",
323+ action: "alertSnapshotLoadFailed"
324+ }));
325+ };
309326 }
310327 function showConfirmationMessage(selectedID, destination) {
311328 if (selectedID.id) {
@@ -632,9 +649,8 @@ window.onload = function () { · 320 unmodified lines
632649 // The last element of the message contents list contains a bunch of options,
633650 // including whether or not we can share stuff
634651 // The other elements of the list contain image paths.
635−
636− if (messageOptions.containsGif) {
637− if (messageOptions.processingGif) {
652+ if (messageOptions.containsGif === true) {
653+ if (messageOptions.processingGif === true) {
638654 imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon
639655 message.image_data.push({ localPath: messageOptions.loadingGifPath });
640656 message.image_data.forEach(function (element, idx) {
@@ -661,10 +677,22 @@ window.onload = function () { · 20 unmodified lines
661677 break;
662678 case 'captureSettings':
663679 handleCaptureSetting(message.setting);
680+ break;
681+ case 'setPrintButtonEnabled':
682+ setPrintButtonEnabled();
683+ break;
684+ case 'setPrintButtonLoading':
685+ setPrintButtonLoading();
686+ break;
687+ case 'setPrintButtonDisabled':
688+ setPrintButtonDisabled();
664689 break;
665690 case 'snapshotUploadComplete':
666− var isGif = message.image_url.split('.').pop().toLowerCase() === "gif";
691+ var isGif = fileExtensionMatches(message.image_url, "gif");
667692 updateShareInfo(isGif ? "p1" : "p0", message.story_id);
693+ if (isPrintProcessing()) {
694+ setPrintButtonEnabled();
695+ }
668696 break;
669697 default:
670698 console.log("Unknown message action received in SnapshotReview.js.");
@@ -694,6 +722,59 @@ function takeSnapshot() { · 23 unmodified lines
694722 }
695723 }
696724
725+function isPrintDisabled() {
726+ var printElement = document.getElementById('print-icon');
727+
728+ return printElement.classList.contains("print-icon") &&
729+ printElement.classList.contains("print-icon-default") &&
730+ document.getElementById('print-button').disabled;
731+}
732+function isPrintProcessing() {
733+ var printElement = document.getElementById('print-icon');
734+
735+ return printElement.classList.contains("print-icon") &&
736+ printElement.classList.contains("print-icon-loading") &&
737+ document.getElementById('print-button').disabled;
738+}
739+function isPrintEnabled() {
740+ var printElement = document.getElementById('print-icon');
741+
742+ return printElement.classList.contains("print-icon") &&
743+ printElement.classList.contains("print-icon-default") &&
744+ !document.getElementById('print-button').disabled;
745+}
746+
747+function setPrintButtonLoading() {
748+ document.getElementById('print-icon').className = "print-icon print-icon-loading";
749+ document.getElementById('print-button').disabled = true;
750+}
751+function setPrintButtonDisabled() {
752+ document.getElementById('print-icon').className = "print-icon print-icon-default";
753+ document.getElementById('print-button').disabled = true;
754+}
755+function setPrintButtonEnabled() {
756+ document.getElementById('print-button').disabled = false;
757+ document.getElementById('print-icon').className = "print-icon print-icon-default";
758+}
759+
760+function requestPrintButtonUpdate() {
761+ EventBridge.emitWebEvent(JSON.stringify({
762+ type: "snapshot",
763+ action: "requestPrintButtonUpdate"
764+ }));
765+}
766+
767+function printToPolaroid() {
768+ if (isPrintEnabled()) {
769+ EventBridge.emitWebEvent(JSON.stringify({
770+ type: "snapshot",
771+ action: "printToPolaroid"
772+ }));
773+ } else {
774+ setPrintButtonLoading();
775+ }
776+}
777+
697778 function testInBrowser(test) {
698779 if (test === 0) {
699780 showSetupInstructions();
scripts/system/html/js/entityProperties.js+62−127
@@ -20,7 +20,8 @@ var ICON_FOR_TYPE = { · 19 unmodified lines
2020 Light: "p",
2121 Zone: "o",
2222 PolyVox: "&#xe005;",
23− Multiple: "&#xe000;"
23+ Multiple: "&#xe000;",
24+ PolyLine: "&#xe01b;"
2425 }
2526
2627 var EDITOR_TIMEOUT_DURATION = 1500;
@@ -82,11 +83,23 @@ function showElements(els, show) { · 55 unmodified lines
8283 }
8384 }
8485
86+function updateProperty(propertyName, propertyValue) {
87+ var properties = {};
88+ properties[propertyName] = propertyValue;
89+ updateProperties(properties);
90+}
91+
92+function updateProperties(properties) {
93+ EventBridge.emitWebEvent(JSON.stringify({
94+ id: lastEntityID,
95+ type: "update",
96+ properties: properties
97+ }));
98+}
99+
85100 function createEmitCheckedPropertyUpdateFunction(propertyName) {
86101 return function() {
87− EventBridge.emitWebEvent(
88− '{"id":' + lastEntityID + ', "type":"update", "properties":{"' + propertyName + '":' + this.checked + '}}'
89− );
102+ updateProperty(propertyName, this.checked);
90103 };
91104 }
92105
@@ -105,13 +118,7 @@ function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) { · 12 unmodified lines
105118 var properties = {};
106119 properties[group] = {};
107120 properties[group][propertyName] = this.checked;
108− EventBridge.emitWebEvent(
109− JSON.stringify({
110− id: lastEntityID,
111− type: "update",
112− properties: properties
113− })
114− );
121+ updateProperties(properties);
115122 };
116123 }
117124
@@ -119,10 +126,7 @@ function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { · 1 unmodified line
119126 decimals = decimals == undefined ? 4 : decimals;
120127 return function() {
121128 var value = parseFloat(this.value).toFixed(decimals);
122−
123− EventBridge.emitWebEvent(
124− '{"id":' + lastEntityID + ', "type":"update", "properties":{"' + propertyName + '":' + value + '}}'
125− );
129+ updateProperty(propertyName, value);
126130 };
127131 }
128132
@@ -131,28 +135,14 @@ function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) { · 2 unmodified lines
131135 var properties = {};
132136 properties[group] = {};
133137 properties[group][propertyName] = this.value;
134− EventBridge.emitWebEvent(
135− JSON.stringify({
136− id: lastEntityID,
137− type: "update",
138− properties: properties,
139− })
140− );
138+ updateProperties(properties);
141139 };
142140 }
143141
144142
145143 function createEmitTextPropertyUpdateFunction(propertyName) {
146144 return function() {
147− var properties = {};
148− properties[propertyName] = this.value;
149− EventBridge.emitWebEvent(
150− JSON.stringify({
151− id: lastEntityID,
152− type: "update",
153− properties: properties,
154− })
155− );
145+ updateProperty(propertyName, this.value);
156146 };
157147 }
158148
@@ -161,62 +151,44 @@ function createEmitGroupTextPropertyUpdateFunction(group, propertyName) { · 2 unmodified lines
161151 var properties = {};
162152 properties[group] = {};
163153 properties[group][propertyName] = this.value;
164− EventBridge.emitWebEvent(
165− JSON.stringify({
166− id: lastEntityID,
167− type: "update",
168− properties: properties,
169− })
170− );
154+ updateProperties(properties);
171155 };
172156 }
173157
174158 function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) {
175159 return function() {
176− var data = {
177− id: lastEntityID,
178− type: "update",
179− properties: {}
180− };
181− data.properties[property] = {
160+ var properties = {};
161+ properties[property] = {
182162 x: elX.value,
183163 y: elY.value,
184164 z: elZ.value,
185165 };
186− EventBridge.emitWebEvent(JSON.stringify(data));
166+ updateProperties(properties);
187167 }
188168 };
189169
190170 function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) {
191171 return function() {
192− var data = {
193− id: lastEntityID,
194− type: "update",
195− properties: {}
196− };
197− data.properties[group] = {};
198− data.properties[group][property] = {
172+ var properties = {};
173+ properties[group] = {};
174+ properties[group][property] = {
199175 x: elX.value,
200176 y: elY.value,
201177 z: elZ ? elZ.value : 0,
202178 };
203− EventBridge.emitWebEvent(JSON.stringify(data));
179+ updateProperties(properties);
204180 }
205181 };
206182
207183 function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) {
208184 return function() {
209− var data = {
210− id: lastEntityID,
211− type: "update",
212− properties: {}
213− };
214− data.properties[property] = {
185+ var properties = {};
186+ properties[property] = {
215187 x: elX.value * multiplier,
216188 y: elY.value * multiplier,
217189 z: elZ.value * multiplier,
218190 };
219− EventBridge.emitWebEvent(JSON.stringify(data));
191+ updateProperties(properties);
220192 }
221193 };
222194
@@ -227,44 +199,35 @@ function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue) · 4 unmodified lines
227199 };
228200
229201 function emitColorPropertyUpdate(property, red, green, blue, group) {
230− var data = {
231− id: lastEntityID,
232− type: "update",
233− properties: {}
234− };
202+ var properties = {};
235203 if (group) {
236− data.properties[group] = {};
237− data.properties[group][property] = {
204+ properties[group] = {};
205+ properties[group][property] = {
238206 red: red,
239207 green: green,
240208 blue: blue,
241209 };
242210 } else {
243− data.properties[property] = {
211+ properties[property] = {
244212 red: red,
245213 green: green,
246214 blue: blue,
247215 };
248216 }
249− EventBridge.emitWebEvent(JSON.stringify(data));
217+ updateProperties(properties);
250218 };
251219
252220
253221 function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) {
254222 return function() {
255− var data = {
256− id: lastEntityID,
257− type: "update",
258− properties: {}
259− };
260− data.properties[group] = {};
261−
262− data.properties[group][property] = {
223+ var properties = {};
224+ properties[group] = {};
225+ properties[group][property] = {
263226 red: elRed.value,
264227 green: elGreen.value,
265228 blue: elBlue.value,
266229 };
267− EventBridge.emitWebEvent(JSON.stringify(data));
230+ updateProperties(properties);
268231 }
269232 };
270233
@@ -277,18 +240,7 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen · 6 unmodified lines
277240 // We've unchecked, so remove
278241 propertyValue = propertyValue.replace(subPropertyString + ",", "");
279242 }
280−
281− var _properties = {}
282− _properties[propertyName] = propertyValue;
283−
284− EventBridge.emitWebEvent(
285− JSON.stringify({
286− id: lastEntityID,
287− type: "update",
288− properties: _properties
289− })
290− );
291−
243+ updateProperty(propertyName, propertyValue);
292244 }
293245
294246 function setUserDataFromEditor(noUpdate) {
@@ -314,18 +266,11 @@ function setUserDataFromEditor(noUpdate) { · 19 unmodified lines
314266 );
315267 return;
316268 } else {
317− EventBridge.emitWebEvent(
318− JSON.stringify({
319− id: lastEntityID,
320− type: "update",
321− properties: {
322− userData: text
323− },
324− })
325− );
269+ updateProperty('userData', text);
326270 }
327271 }
328272 }
273+
329274 function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) {
330275 var properties = {};
331276 var parsedData = {};
@@ -372,13 +317,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { · 40 unmodified lines
372317
373318 userDataElement.value = properties['userData'];
374319
375− EventBridge.emitWebEvent(
376− JSON.stringify({
377− id: lastEntityID,
378− type: "update",
379− properties: properties,
380− })
381− );
320+ updateProperties(properties);
382321 }
383322 function userDataChanger(groupName, keyName, values, userDataElement, defaultValue) {
384323 var val = {}, def = {};
@@ -900,7 +839,6 @@ function loaded() { · 515 unmodified lines
900839 elCloneable.checked = parsedUserData["grabbableKey"].cloneable;
901840 elCloneableGroup.style.display = elCloneable.checked ? "block": "none";
902841 elCloneableDynamic.checked = parsedUserData["grabbableKey"].cloneDynamic ? parsedUserData["grabbableKey"].cloneDynamic : properties.dynamic;
903− elDynamic.checked = elCloneable.checked ? false: properties.dynamic;
904842 if (elCloneable.checked) {
905843 if ("cloneLifetime" in parsedUserData["grabbableKey"]) {
906844 elCloneableLifetime.value = parsedUserData["grabbableKey"].cloneLifetime ? parsedUserData["grabbableKey"].cloneLifetime : 300;
@@ -1202,8 +1140,8 @@ function loaded() { · 295 unmodified lines
12021140 });
12031141
12041142 elGrabbable.addEventListener('change', function() {
1205− if(elCloneable.checked) {
1206− elGrabbable.checked = false;
1143+ if (elCloneable.checked) {
1144+ elGrabbable.checked = false;
12071145 }
12081146 userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic);
12091147 });
@@ -1213,17 +1151,22 @@ function loaded() { · 3 unmodified lines
12131151 elCloneable.addEventListener('change', function (event) {
12141152 var checked = event.target.checked;
12151153 if (checked) {
1216− multiDataUpdater("grabbableKey",
1217− {cloneLifetime: elCloneableLifetime, cloneLimit: elCloneableLimit, cloneDynamic: elCloneableDynamic, cloneable: event.target},
1218− elUserData, {});
1154+ multiDataUpdater("grabbableKey", {
1155+ cloneLifetime: elCloneableLifetime,
1156+ cloneLimit: elCloneableLimit,
1157+ cloneDynamic: elCloneableDynamic,
1158+ cloneable: event.target,
1159+ grabbable: null
1160+ }, elUserData, {});
12191161 elCloneableGroup.style.display = "block";
1220− EventBridge.emitWebEvent(
1221− '{"id":' + lastEntityID + ', "type":"update", "properties":{"dynamic":false, "grabbable": false}}'
1222− );
1162+ updateProperty('dynamic', false);
12231163 } else {
1224− multiDataUpdater("grabbableKey",
1225− {cloneLifetime: null, cloneLimit: null, cloneDynamic: null, cloneable: false},
1226− elUserData, {});
1164+ multiDataUpdater("grabbableKey", {
1165+ cloneLifetime: null,
1166+ cloneLimit: null,
1167+ cloneDynamic: null,
1168+ cloneable: false
1169+ }, elUserData, {});
12271170 elCloneableGroup.style.display = "none";
12281171 }
12291172 });
@@ -1258,15 +1201,7 @@ function loaded() { · 28 unmodified lines
12581201 showUserDataTextArea();
12591202 showNewJSONEditorButton();
12601203 hideSaveUserDataButton();
1261− var properties = {};
1262− properties['userData'] = elUserData.value;
1263− EventBridge.emitWebEvent(
1264− JSON.stringify({
1265− id: lastEntityID,
1266− type: "update",
1267− properties: properties,
1268− })
1269− );
1204+ updateProperty('userData', elUserData.value)
12701205 });
12711206
12721207 elSaveUserData.addEventListener("click", function() {
scripts/system/html/js/eventBridgeLoader.js+1−1
@@ -13,7 +13,7 @@ var WebChannel; · 12 unmodified lines
1313
1414 openEventBridge = function(callback) {
1515 WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) {
16− EventBridge = WebChannel.objects.eventBridgeWrapper.eventBridge;
16+ EventBridge = WebChannel.objects.eventBridge;
1717 callback(EventBridge);
1818 });
1919 }
scripts/system/html/js/marketplacesInject.js+2−9
@@ -25,9 +25,7 @@ · 24 unmodified lines
2525 var canWriteAssets = false;
2626 var xmlHttpRequest = null;
2727 var isPreparing = false; // Explicitly track download request status.
28−
29− var lastPage = "https://metaverse.highfidelity.com/marketplace?";
30−
28+
3129 function injectCommonCode(isDirectoryPage) {
3230
3331 // Supporting styles from marketplaces.css.
@@ -67,7 +65,7 @@ · 33 unmodified lines
6765
6866 // Footer actions.
6967 $("#back-button").on("click", function () {
70− window.location = lastPage;
68+ window.history.back();
7169 });
7270 $("#all-markets").on("click", function () {
7371 EventBridge.emitWebEvent(GOTO_DIRECTORY);
@@ -344,12 +342,7 @@ · 270 unmodified lines
344342 }
345343 }
346344
347− function locationChanged() {
348− lastPage = location.href;
349− }
350−
351345 // Load / unload.
352346 window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready().
353− window.addEventListener("hashchange", locationChanged);
354347
355348 }());
scripts/system/libraries/WebTablet.js+38−31
@@ -22,7 +22,9 @@ var DEFAULT_WIDTH = 0.4375; · 21 unmodified lines
2222 var DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees
2323 var SENSOR_TO_ROOM_MATRIX = -2;
2424 var CAMERA_MATRIX = -7;
25−var ROT_Y_180 = {x: 0, y: 1, z: 0, w: 0};
25+var ROT_Y_180 = {x: 0.0, y: 1.0, z: 0, w: 0};
26+var ROT_LANDSCAPE = {x: 1.0, y: 1.0, z: 0, w: 0};
27+var ROT_LANDSCAPE_WINDOW = {x: 0.0, y: 0.0, z: 0.0, w: 0};
2628 var ROT_IDENT = {x: 0, y: 0, z: 0, w: 1};
2729 var TABLET_TEXTURE_RESOLUTION = { x: 480, y: 706 };
2830 var INCHES_TO_METERS = 1 / 39.3701;
@@ -41,7 +43,7 @@ var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home- · 12 unmodified lines
4143 // returns object with two fields:
4244 // * position - position in front of the user
4345 // * rotation - rotation of entity so it faces the user.
44−function calcSpawnInfo(hand, height) {
46+function calcSpawnInfo(hand, tabletHeight) {
4547 var finalPosition;
4648
4749 var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position;
@@ -51,30 +53,35 @@ function calcSpawnInfo(hand, height) { · 3 unmodified lines
5153 hand = NO_HANDS;
5254 }
5355
56+ var handController = null;
5457 if (HMD.active && hand !== NO_HANDS) {
55− var handController = getControllerWorldLocation(hand, true);
56−
57− var TABLET_UP_OFFSET = 0.1;
58− var TABLET_FORWARD_OFFSET = 0.1;
59− var normal = Vec3.multiplyQbyV(handController.rotation, {x: 0, y: -1, z: 0});
60− var pitch = Math.asin(normal.y);
61− var MAX_PITCH = Math.PI / 4;
62− if (pitch < -MAX_PITCH) {
63− pitch = -MAX_PITCH;
64− } else if (pitch > MAX_PITCH) {
65− pitch = MAX_PITCH;
66− }
58+ handController = getControllerWorldLocation(hand, true);
59+ }
60+
61+ if (handController && handController.valid) {
62+ // Orient tablet per hand pitch and yaw.
63+ // Angle it back similar to holding it like a book.
64+ // Move tablet up so that hand is at bottom.
65+ // Move tablet back so that hand is in front.
6766
68− // rebuild normal from pitch and heading.
69− var heading = Math.atan2(normal.z, normal.x);
70− normal = {x: Math.cos(heading), y: Math.sin(pitch), z: Math.sin(heading)};
67+ var position = handController.position;
68+ var rotation = handController.rotation;
69+
70+ if (hand === Controller.Standard.LeftHand) {
71+ rotation = Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0));
72+ } else {
73+ rotation = Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, -90, 0));
74+ }
75+ var normal = Vec3.multiplyQbyV(rotation, Vec3.UNIT_NEG_Y);
76+ var lookAt = Quat.lookAt(Vec3.ZERO, normal, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y));
77+ var TABLET_RAKE_ANGLE = 30;
78+ rotation = Quat.multiply(Quat.angleAxis(TABLET_RAKE_ANGLE, Vec3.multiplyQbyV(lookAt, Vec3.UNIT_X)), lookAt);
7179
72− var position = Vec3.sum(handController.position, {x: 0, y: TABLET_UP_OFFSET, z: 0});
73− var rotation = Quat.lookAt({x: 0, y: 0, z: 0}, normal, Y_AXIS);
74− var offset = Vec3.multiplyQbyV(rotation, {x: 0, y: height / 2, z: TABLET_FORWARD_OFFSET});
80+ var RELATIVE_SPAWN_OFFSET = { x: 0, y: 0.4, z: 0.05 };
81+ position = Vec3.sum(position, Vec3.multiplyQbyV(rotation, Vec3.multiply(tabletHeight, RELATIVE_SPAWN_OFFSET)));
7582
7683 return {
77− position: Vec3.sum(offset, position),
84+ position: position,
7885 rotation: rotation
7986 };
8087 } else {
@@ -243,29 +250,29 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { · 162 unmodified lines
243250 };
244251
245252 WebTablet.prototype.getDimensions = function() {
246− if (this.landscape) {
247− return { x: this.width * 2, y: this.height, z: this.depth };
248− } else {
249− return { x: this.width, y: this.height, z: this.depth };
250− }
253+ return { x: this.width, y: this.height, z: this.depth };
251254 };
252255
253256 WebTablet.prototype.getTabletTextureResolution = function() {
254257 if (this.landscape) {
255− return { x: TABLET_TEXTURE_RESOLUTION.x * 2, y: TABLET_TEXTURE_RESOLUTION.y };
258+ return { x: TABLET_TEXTURE_RESOLUTION.y , y: TABLET_TEXTURE_RESOLUTION.x };
256259 } else {
257260 return TABLET_TEXTURE_RESOLUTION;
258261 }
259262 };
260263
261264 WebTablet.prototype.setLandscape = function(newLandscapeValue) {
262− if (this.landscape == newLandscapeValue) {
265+ if (this.landscape === newLandscapeValue) {
263266 return;
264267 }
268+
265269 this.landscape = newLandscapeValue;
266− Overlays.editOverlay(this.tabletEntityID, { dimensions: this.getDimensions() });
270+ Overlays.editOverlay(this.tabletEntityID,
271+ { rotation: this.landscape ? Quat.multiply(Camera.orientation, ROT_LANDSCAPE) :
272+ Quat.multiply(Camera.orientation, ROT_Y_180) });
267273 Overlays.editOverlay(this.webOverlayID, {
268− resolution: this.getTabletTextureResolution()
274+ resolution: this.getTabletTextureResolution(),
275+ rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW)
269276 });
270277 };
271278
@@ -407,7 +414,7 @@ WebTablet.prototype.calculateWorldAttitudeRelativeToCamera = function (windowPos · 135 unmodified lines
407414
408415 return {
409416 position: worldMousePosition,
410− rotation: Quat.multiply(Camera.orientation, ROT_Y_180)
417+ rotation: this.landscape ? Quat.multiply(Camera.orientation, ROT_LANDSCAPE) : Quat.multiply(Camera.orientation, ROT_Y_180)
411418 };
412419 };
413420
scripts/system/libraries/entityList.js+7−1
@@ -109,7 +109,13 @@ EntityListTool = function(opts) { · 108 unmodified lines
109109 };
110110
111111 webView.webEventReceived.connect(function(data) {
112− data = JSON.parse(data);
112+ try {
113+ data = JSON.parse(data);
114+ } catch(e) {
115+ print("entityList.js: Error parsing JSON: " + e.name + " data " + data)
116+ return;
117+ }
118+
113119 if (data.type == "selectionUpdate") {
114120 var ids = data.entityIds;
115121 var entityIDs = [];
scripts/system/libraries/gridTool.js+22−8
@@ -79,7 +79,7 @@ Grid = function(opts) { · 78 unmodified lines
7979 }
8080 }
8181
82− that.snapToSurface = function(position, dimensions) {
82+ that.snapToSurface = function(position, dimensions, registration) {
8383 if (!snapToGrid) {
8484 return position;
8585 }
@@ -88,14 +88,18 @@ Grid = function(opts) { · 2 unmodified lines
8888 dimensions = { x: 0, y: 0, z: 0 };
8989 }
9090
91+ if (registration === undefined) {
92+ registration = { x: 0.5, y: 0.5, z: 0.5 };
93+ }
94+
9195 return {
9296 x: position.x,
93− y: origin.y + (dimensions.y / 2),
97+ y: origin.y + (registration.y * dimensions.y),
9498 z: position.z
9599 };
96100 }
97101
98− that.snapToGrid = function(position, majorOnly, dimensions) {
102+ that.snapToGrid = function(position, majorOnly, dimensions, registration) {
99103 if (!snapToGrid) {
100104 return position;
101105 }
@@ -104,6 +108,10 @@ Grid = function(opts) { · 2 unmodified lines
104108 dimensions = { x: 0, y: 0, z: 0 };
105109 }
106110
111+ if (registration === undefined) {
112+ registration = { x: 0.5, y: 0.5, z: 0.5 };
113+ }
114+
107115 var spacing = majorOnly ? majorGridEvery : minorGridEvery;
108116
109117 position = Vec3.subtract(position, origin);
@@ -112,7 +120,7 @@ Grid = function(opts) { · 2 unmodified lines
112120 position.y = Math.round(position.y / spacing) * spacing;
113121 position.z = Math.round(position.z / spacing) * spacing;
114122
115− return Vec3.sum(Vec3.sum(position, Vec3.multiply(0.5, dimensions)), origin);
123+ return Vec3.sum(Vec3.sum(position, Vec3.multiplyVbyV(registration, dimensions)), origin);
116124 }
117125
118126 that.snapToSpacing = function(delta, majorOnly) {
@@ -161,9 +169,9 @@ Grid = function(opts) { · 42 unmodified lines
161169
162170 if (data.origin) {
163171 var pos = data.origin;
164− pos.x = pos.x === undefined ? origin.x : pos.x;
165− pos.y = pos.y === undefined ? origin.y : pos.y;
166− pos.z = pos.z === undefined ? origin.z : pos.z;
172+ pos.x = pos.x === undefined ? origin.x : parseFloat(pos.x);
173+ pos.y = pos.y === undefined ? origin.y : parseFloat(pos.y);
174+ pos.z = pos.z === undefined ? origin.z : parseFloat(pos.z);
167175 that.setPosition(pos, true);
168176 }
169177
@@ -238,7 +246,13 @@ GridTool = function(opts) { · 68 unmodified lines
238246 });
239247
240248 webView.webEventReceived.connect(function(data) {
241− data = JSON.parse(data);
249+ try {
250+ data = JSON.parse(data);
251+ } catch(e) {
252+ print("gridTool.js: Error parsing JSON: " + e.name + " data " + data)
253+ return;
254+ }
255+
242256 if (data.type == "init") {
243257 horizontalGrid.emitUpdate();
244258 } else if (data.type == "update") {
scripts/system/makeUserConnection.js+2−3
@@ -613,7 +613,6 @@ · 612 unmodified lines
613613 error = "All participants must be logged in to connect.";
614614 }
615615 result = error ? {status: 'error', connection: error} : response;
616− UserActivityLogger.makeUserConnection(connectingId, false, error || response);
617616 connectionRequestCompleted();
618617 } else {
619618 result = response;
@@ -668,8 +667,8 @@ · 48 unmodified lines
668667 // to be sure the hand is still close enough. If not, we terminate
669668 // the interval, go back to the waiting state. If we make it
670669 // the entire CONNECTING_TIME, we make the connection. We pass in
671− // whether or not the connecting id is actually logged in, as now we
672− // will allow to start the connection process but have it stop with a
670+ // whether or not the connecting id is actually logged in, as now we
671+ // will allow to start the connection process but have it stop with a
673672 // fail message before trying to call the backend if the other guy isn't
674673 // logged in.
675674 function startConnecting(id, jointIndex, isLoggedIn) {
scripts/system/particle_explorer/dat.gui.min.js+0−95
@@ -1,95 +0,0 @@
1−/**
2− * dat-gui JavaScript Controller Library
3− * http://code.google.com/p/dat-gui
4− *
5− * Copyright 2011 Data Arts Team, Google Creative Lab
6− *
7− * Licensed under the Apache License, Version 2.0 (the "License");
8− * you may not use this file except in compliance with the License.
9− * You may obtain a copy of the License at
10− *
11− * http://www.apache.org/licenses/LICENSE-2.0
12− */
13−var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(f,a){a=a||document;var d=a.createElement("link");d.type="text/css";d.rel="stylesheet";d.href=f;a.getElementsByTagName("head")[0].appendChild(d)},inject:function(f,a){a=a||document;var d=document.createElement("style");d.type="text/css";d.innerHTML=f;a.getElementsByTagName("head")[0].appendChild(d)}}}();
14−dat.utils.common=function(){var f=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(a[c])||(d[c]=a[c])},this);return d},defaults:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(d[c])&&(d[c]=a[c])},this);return d},compose:function(){var d=a.call(arguments);return function(){for(var e=a.call(arguments),c=d.length-1;0<=c;c--)e=[d[c].apply(this,e)];return e[0]}},
15−each:function(a,e,c){if(a)if(f&&a.forEach&&a.forEach===f)a.forEach(e,c);else if(a.length===a.length+0)for(var b=0,p=a.length;b<p&&!(b in a&&e.call(c,a[b],b)===this.BREAK);b++);else for(b in a)if(e.call(c,a[b],b)===this.BREAK)break},defer:function(a){setTimeout(a,0)},toArray:function(d){return d.toArray?d.toArray():a.call(d)},isUndefined:function(a){return void 0===a},isNull:function(a){return null===a},isNaN:function(a){return a!==a},isArray:Array.isArray||function(a){return a.constructor===Array},
16−isObject:function(a){return a===Object(a)},isNumber:function(a){return a===a+0},isString:function(a){return a===a+""},isBoolean:function(a){return!1===a||!0===a},isFunction:function(a){return"[object Function]"===Object.prototype.toString.call(a)}}}();
17−dat.controllers.Controller=function(f){var a=function(a,e){this.initialValue=a[e];this.domElement=document.createElement("div");this.object=a;this.property=e;this.__onFinishChange=this.__onChange=void 0};f.extend(a.prototype,{onChange:function(a){this.__onChange=a;return this},onFinishChange:function(a){this.__onFinishChange=a;return this},setValue:function(a){this.object[this.property]=a;this.__onChange&&this.__onChange.call(this,a);this.updateDisplay();return this},getValue:function(){return this.object[this.property]},
18−updateDisplay:function(){return this},isModified:function(){return this.initialValue!==this.getValue()}});return a}(dat.utils.common);
19−dat.dom.dom=function(f){function a(b){if("0"===b||f.isUndefined(b))return 0;b=b.match(e);return f.isNull(b)?0:parseFloat(b[1])}var d={};f.each({HTMLEvents:["change"],MouseEvents:["click","mousemove","mousedown","mouseup","mouseover"],KeyboardEvents:["keydown"]},function(b,a){f.each(b,function(b){d[b]=a})});var e=/(\d+(\.\d+)?)px/,c={makeSelectable:function(b,a){void 0!==b&&void 0!==b.style&&(b.onselectstart=a?function(){return!1}:function(){},b.style.MozUserSelect=a?"auto":"none",b.style.KhtmlUserSelect=
20−a?"auto":"none",b.unselectable=a?"on":"off")},makeFullscreen:function(b,a,c){f.isUndefined(a)&&(a=!0);f.isUndefined(c)&&(c=!0);b.style.position="absolute";a&&(b.style.left=0,b.style.right=0);c&&(b.style.top=0,b.style.bottom=0)},fakeEvent:function(b,a,c,e){c=c||{};var r=d[a];if(!r)throw Error("Event type "+a+" not supported.");var n=document.createEvent(r);switch(r){case "MouseEvents":n.initMouseEvent(a,c.bubbles||!1,c.cancelable||!0,window,c.clickCount||1,0,0,c.x||c.clientX||0,c.y||c.clientY||0,!1,
21−!1,!1,!1,0,null);break;case "KeyboardEvents":r=n.initKeyboardEvent||n.initKeyEvent;f.defaults(c,{cancelable:!0,ctrlKey:!1,altKey:!1,shiftKey:!1,metaKey:!1,keyCode:void 0,charCode:void 0});r(a,c.bubbles||!1,c.cancelable,window,c.ctrlKey,c.altKey,c.shiftKey,c.metaKey,c.keyCode,c.charCode);break;default:n.initEvent(a,c.bubbles||!1,c.cancelable||!0)}f.defaults(n,e);b.dispatchEvent(n)},bind:function(a,e,d,f){a.addEventListener?a.addEventListener(e,d,f||!1):a.attachEvent&&a.attachEvent("on"+e,d);return c},
22−unbind:function(a,e,d,f){a.removeEventListener?a.removeEventListener(e,d,f||!1):a.detachEvent&&a.detachEvent("on"+e,d);return c},addClass:function(a,e){if(void 0===a.className)a.className=e;else if(a.className!==e){var d=a.className.split(/ +/);-1==d.indexOf(e)&&(d.push(e),a.className=d.join(" ").replace(/^\s+/,"").replace(/\s+$/,""))}return c},removeClass:function(a,e){if(e){if(void 0!==a.className)if(a.className===e)a.removeAttribute("class");else{var d=a.className.split(/ +/),f=d.indexOf(e);-1!=
23−f&&(d.splice(f,1),a.className=d.join(" "))}}else a.className=void 0;return c},hasClass:function(a,c){return(new RegExp("(?:^|\\s+)"+c+"(?:\\s+|$)")).test(a.className)||!1},getWidth:function(b){b=getComputedStyle(b);return a(b["border-left-width"])+a(b["border-right-width"])+a(b["padding-left"])+a(b["padding-right"])+a(b.width)},getHeight:function(b){b=getComputedStyle(b);return a(b["border-top-width"])+a(b["border-bottom-width"])+a(b["padding-top"])+a(b["padding-bottom"])+a(b.height)},getOffset:function(a){var c=
24−{left:0,top:0};if(a.offsetParent){do c.left+=a.offsetLeft,c.top+=a.offsetTop;while(a=a.offsetParent)}return c},isActive:function(a){return a===document.activeElement&&(a.type||a.href)}};return c}(dat.utils.common);
25−dat.controllers.OptionController=function(f,a,d){var e=function(c,b,f){e.superclass.call(this,c,b);var q=this;this.__select=document.createElement("select");if(d.isArray(f)){var l={};d.each(f,function(a){l[a]=a});f=l}d.each(f,function(a,b){var c=document.createElement("option");c.innerHTML=b;c.setAttribute("value",a);q.__select.appendChild(c)});this.updateDisplay();a.bind(this.__select,"change",function(){q.setValue(this.options[this.selectedIndex].value)});this.domElement.appendChild(this.__select)};
26−e.superclass=f;d.extend(e.prototype,f.prototype,{setValue:function(a){a=e.superclass.prototype.setValue.call(this,a);this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue());return a},updateDisplay:function(){this.__select.value=this.getValue();return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common);
27−dat.controllers.NumberController=function(f,a){function d(a){a=a.toString();return-1<a.indexOf(".")?a.length-a.indexOf(".")-1:0}var e=function(c,b,f){e.superclass.call(this,c,b);f=f||{};this.__min=f.min;this.__max=f.max;this.__step=f.step;a.isUndefined(this.__step)?this.__impliedStep=0==this.initialValue?1:Math.pow(10,Math.floor(Math.log(Math.abs(this.initialValue))/Math.LN10))/10:this.__impliedStep=this.__step;this.__precision=d(this.__impliedStep)};e.superclass=f;a.extend(e.prototype,f.prototype,
28−{setValue:function(a){void 0!==this.__min&&a<this.__min?a=this.__min:void 0!==this.__max&&a>this.__max&&(a=this.__max);void 0!==this.__step&&0!=a%this.__step&&(a=Math.round(a/this.__step)*this.__step);return e.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__impliedStep=this.__step=a;this.__precision=d(a);return this}});return e}(dat.controllers.Controller,dat.utils.common);
29−dat.controllers.NumberControllerBox=function(f,a,d){var e=function(c,b,f){function q(){var a=parseFloat(n.__input.value);d.isNaN(a)||n.setValue(a)}function l(a){var b=u-a.clientY;n.setValue(n.getValue()+b*n.__impliedStep);u=a.clientY}function r(){a.unbind(window,"mousemove",l);a.unbind(window,"mouseup",r)}this.__truncationSuspended=!1;e.superclass.call(this,c,b,f);var n=this,u;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",q);a.bind(this.__input,
30−"blur",function(){q();n.__onFinishChange&&n.__onFinishChange.call(n,n.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",l);a.bind(window,"mouseup",r);u=b.clientY});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&(n.__truncationSuspended=!0,this.blur(),n.__truncationSuspended=!1)});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype,f.prototype,{updateDisplay:function(){var a=this.__input,b;if(this.__truncationSuspended)b=
31−this.getValue();else{b=this.getValue();var d=Math.pow(10,this.__precision);b=Math.round(b*d)/d}a.value=b;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common);
32−dat.controllers.NumberControllerSlider=function(f,a,d,e,c){function b(a,b,c,e,d){return e+(a-b)/(c-b)*(d-e)}var p=function(c,e,d,f,u){function A(c){c.preventDefault();var e=a.getOffset(k.__background),d=a.getWidth(k.__background);k.setValue(b(c.clientX,e.left,e.left+d,k.__min,k.__max));return!1}function g(){a.unbind(window,"mousemove",A);a.unbind(window,"mouseup",g);k.__onFinishChange&&k.__onFinishChange.call(k,k.getValue())}p.superclass.call(this,c,e,{min:d,max:f,step:u});var k=this;this.__background=
33−document.createElement("div");this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",A);a.bind(window,"mouseup",g);A(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};p.superclass=f;p.useDefaultStyles=function(){d.inject(c)};e.extend(p.prototype,f.prototype,{updateDisplay:function(){var a=
34−(this.getValue()-this.__min)/(this.__max-this.__min);this.__foreground.style.width=100*a+"%";return p.superclass.prototype.updateDisplay.call(this)}});return p}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,"/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}");
35−dat.controllers.FunctionController=function(f,a,d){var e=function(c,b,d){e.superclass.call(this,c,b);var f=this;this.__button=document.createElement("div");this.__button.innerHTML=void 0===d?"Fire":d;a.bind(this.__button,"click",function(a){a.preventDefault();f.fire();return!1});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};e.superclass=f;d.extend(e.prototype,f.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.getValue().call(this.object);
36−this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common);
37−dat.controllers.BooleanController=function(f,a,d){var e=function(c,b){e.superclass.call(this,c,b);var d=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){d.setValue(!d.__prev)},!1);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};e.superclass=f;d.extend(e.prototype,f.prototype,{setValue:function(a){a=e.superclass.prototype.setValue.call(this,a);this.__onFinishChange&&
38−this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){!0===this.getValue()?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=!0):this.__checkbox.checked=!1;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common);
39−dat.color.toString=function(f){return function(a){if(1==a.a||f.isUndefined(a.a)){for(a=a.hex.toString(16);6>a.length;)a="0"+a;return"#"+a}return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common);
40−dat.color.interpret=function(f,a){var d,e,c=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:f},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:f},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);
41−return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:f},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:f}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return 3!=
42−a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return 4!=a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&&
43−a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){e=!1;
44−var b=1<arguments.length?a.toArray(arguments):arguments[0];a.each(c,function(c){if(c.litmus(b))return a.each(c.conversions,function(c,f){d=c.read(b);if(!1===e&&!1!==d)return e=d,d.conversionName=f,d.conversion=c,a.BREAK}),a.BREAK});return e}}(dat.color.toString,dat.utils.common);
45−dat.GUI=dat.gui.GUI=function(f,a,d,e,c,b,p,q,l,r,n,u,A,g,k){function v(a,b,h,d){if(void 0===b[h])throw Error("Object "+b+' has no property "'+h+'"');d.color?b=new n(b,h):(b=[b,h].concat(d.factoryArgs),b=e.apply(a,b));d.before instanceof c&&(d.before=d.before.__li);y(a,b);g.addClass(b.domElement,"c");h=document.createElement("span");g.addClass(h,"property-name");h.innerHTML=b.property;var f=document.createElement("div");f.appendChild(h);f.appendChild(b.domElement);d=w(a,f,d.before);g.addClass(d,m.CLASS_CONTROLLER_ROW);
46−g.addClass(d,typeof b.getValue());t(a,d,b);a.__controllers.push(b);return b}function w(a,b,h){var c=document.createElement("li");b&&c.appendChild(b);h?a.__ul.insertBefore(c,params.before):a.__ul.appendChild(c);a.onResize();return c}function t(a,c,h){h.__li=c;h.__gui=a;k.extend(h,{options:function(b){if(1<arguments.length)return h.remove(),v(a,h.object,h.property,{before:h.__li.nextElementSibling,factoryArgs:[k.toArray(arguments)]});if(k.isArray(b)||k.isObject(b))return h.remove(),v(a,h.object,h.property,
47−{before:h.__li.nextElementSibling,factoryArgs:[b]})},name:function(a){h.__li.firstElementChild.firstElementChild.innerHTML=a;return h},listen:function(){h.__gui.listen(h);return h},remove:function(){h.__gui.remove(h);return h}});if(h instanceof l){var d=new q(h.object,h.property,{min:h.__min,max:h.__max,step:h.__step});k.each(["updateDisplay","onChange","onFinishChange"],function(a){var K=h[a],b=d[a];h[a]=d[a]=function(){var a=Array.prototype.slice.call(arguments);K.apply(h,a);return b.apply(d,a)}});
48−g.addClass(c,"has-slider");h.domElement.insertBefore(d.domElement,h.domElement.firstElementChild)}else if(h instanceof q){var e=function(b){return k.isNumber(h.__min)&&k.isNumber(h.__max)?(h.remove(),v(a,h.object,h.property,{before:h.__li.nextElementSibling,factoryArgs:[h.__min,h.__max,h.__step]})):b};h.min=k.compose(e,h.min);h.max=k.compose(e,h.max)}else h instanceof b?(g.bind(c,"click",function(){g.fakeEvent(h.__checkbox,"click")}),g.bind(h.__checkbox,"click",function(a){a.stopPropagation()})):
49−h instanceof p?(g.bind(c,"click",function(){g.fakeEvent(h.__button,"click")}),g.bind(c,"mouseover",function(){g.addClass(h.__button,"hover")}),g.bind(c,"mouseout",function(){g.removeClass(h.__button,"hover")})):h instanceof n&&(g.addClass(c,"color"),h.updateDisplay=k.compose(function(a){c.style.borderLeftColor=h.__color.toString();return a},h.updateDisplay),h.updateDisplay());h.setValue=k.compose(function(b){a.getRoot().__preset_select&&h.isModified()&&E(a.getRoot(),!0);return b},h.setValue)}function y(a,
50−b){var c=a.getRoot(),d=c.__rememberedObjects.indexOf(b.object);if(-1!=d){var e=c.__rememberedObjectIndecesToControllers[d];void 0===e&&(e={},c.__rememberedObjectIndecesToControllers[d]=e);e[b.property]=b;if(c.load&&c.load.remembered){c=c.load.remembered;if(c[a.preset])c=c[a.preset];else if(c.Default)c=c.Default;else return;c[d]&&void 0!==c[d][b.property]&&(d=c[d][b.property],b.initialValue=d,b.setValue(d))}}}function L(a){var b=a.__save_row=document.createElement("li");g.addClass(a.domElement,"has-save");
51−a.__ul.insertBefore(b,a.__ul.firstChild);g.addClass(b,"save-row");var c=document.createElement("span");c.innerHTML="&nbsp;";g.addClass(c,"button gears");var d=document.createElement("span");d.innerHTML="Save";g.addClass(d,"button");g.addClass(d,"save");var e=document.createElement("span");e.innerHTML="New";g.addClass(e,"button");g.addClass(e,"save-as");var f=document.createElement("span");f.innerHTML="Revert";g.addClass(f,"button");g.addClass(f,"revert");var r=a.__preset_select=document.createElement("select");
52−a.load&&a.load.remembered?k.each(a.load.remembered,function(b,c){F(a,c,c==a.preset)}):F(a,"Default",!1);g.bind(r,"change",function(){for(var b=0;b<a.__preset_select.length;b++)a.__preset_select[b].innerHTML=a.__preset_select[b].value;a.preset=this.value});b.appendChild(r);b.appendChild(c);b.appendChild(d);b.appendChild(e);b.appendChild(f);if(x){var n=function(){u.style.display=a.useLocalStorage?"block":"none"},b=document.getElementById("dg-save-locally"),u=document.getElementById("dg-local-explain");
53−b.style.display="block";b=document.getElementById("dg-local-storage");"true"===localStorage.getItem(document.location.href+".isLocal")&&b.setAttribute("checked","checked");n();g.bind(b,"change",function(){a.useLocalStorage=!a.useLocalStorage;n()})}var m=document.getElementById("dg-new-constructor");g.bind(m,"keydown",function(a){!a.metaKey||67!==a.which&&67!=a.keyCode||B.hide()});g.bind(c,"click",function(){m.innerHTML=JSON.stringify(a.getSaveObject(),void 0,2);B.show();m.focus();m.select()});g.bind(d,
54−"click",function(){a.save()});g.bind(e,"click",function(){var b=prompt("Enter a new preset name.");b&&a.saveAs(b)});g.bind(f,"click",function(){a.revert()})}function M(a){function b(f){f.preventDefault();e=f.clientX;g.addClass(a.__closeButton,m.CLASS_DRAG);g.bind(window,"mousemove",c);g.bind(window,"mouseup",d);return!1}function c(b){b.preventDefault();a.width+=e-b.clientX;a.onResize();e=b.clientX;return!1}function d(){g.removeClass(a.__closeButton,m.CLASS_DRAG);g.unbind(window,"mousemove",c);g.unbind(window,
55−"mouseup",d)}a.__resize_handle=document.createElement("div");k.extend(a.__resize_handle.style,{width:"6px",marginLeft:"-3px",height:"200px",cursor:"ew-resize",position:"absolute"});var e;g.bind(a.__resize_handle,"mousedown",b);g.bind(a.__closeButton,"mousedown",b);a.domElement.insertBefore(a.__resize_handle,a.domElement.firstElementChild)}function G(a,b){a.domElement.style.width=b+"px";a.__save_row&&a.autoPlace&&(a.__save_row.style.width=b+"px");a.__closeButton&&(a.__closeButton.style.width=b+"px")}
56−function C(a,b){var c={};k.each(a.__rememberedObjects,function(d,e){var f={};k.each(a.__rememberedObjectIndecesToControllers[e],function(a,c){f[c]=b?a.initialValue:a.getValue()});c[e]=f});return c}function F(a,b,c){var d=document.createElement("option");d.innerHTML=b;d.value=b;a.__preset_select.appendChild(d);c&&(a.__preset_select.selectedIndex=a.__preset_select.length-1)}function E(a,b){var c=a.__preset_select[a.__preset_select.selectedIndex];c.innerHTML=b?c.value+"*":c.value}function H(a){0!=a.length&&
57−u(function(){H(a)});k.each(a,function(a){a.updateDisplay()})}f.inject(d);var x;try{x="localStorage"in window&&null!==window.localStorage}catch(N){x=!1}var B,I=!0,z,D=!1,J=[],m=function(a){function b(){var a=c.getRoot();a.width+=1;k.defer(function(){--a.width})}var c=this;this.domElement=document.createElement("div");this.__ul=document.createElement("ul");this.domElement.appendChild(this.__ul);g.addClass(this.domElement,"dg");this.__folders={};this.__controllers=[];this.__rememberedObjects=[];this.__rememberedObjectIndecesToControllers=
58−[];this.__listening=[];a=a||{};a=k.defaults(a,{autoPlace:!0,width:m.DEFAULT_WIDTH});a=k.defaults(a,{resizable:a.autoPlace,hideable:a.autoPlace});k.isUndefined(a.load)?a.load={preset:"Default"}:a.preset&&(a.load.preset=a.preset);k.isUndefined(a.parent)&&a.hideable&&J.push(this);a.resizable=k.isUndefined(a.parent)&&a.resizable;a.autoPlace&&k.isUndefined(a.scrollable)&&(a.scrollable=!0);var d=x&&"true"===localStorage.getItem(document.location.href+".isLocal"),e;Object.defineProperties(this,{parent:{get:function(){return a.parent}},
59−scrollable:{get:function(){return a.scrollable}},autoPlace:{get:function(){return a.autoPlace}},preset:{get:function(){return c.parent?c.getRoot().preset:a.load.preset},set:function(b){c.parent?c.getRoot().preset=b:a.load.preset=b;for(b=0;b<this.__preset_select.length;b++)this.__preset_select[b].value==this.preset&&(this.__preset_select.selectedIndex=b);c.revert()}},width:{get:function(){return a.width},set:function(b){a.width=b;G(c,b)}},name:{get:function(){return a.name},set:function(b){a.name=
60−b;r&&(r.innerHTML=a.name)}},closed:{get:function(){return a.closed},set:function(b){a.closed=b;a.closed?g.addClass(c.__ul,m.CLASS_CLOSED):g.removeClass(c.__ul,m.CLASS_CLOSED);this.onResize();c.__closeButton&&(c.__closeButton.innerHTML=b?m.TEXT_OPEN:m.TEXT_CLOSED)}},load:{get:function(){return a.load}},useLocalStorage:{get:function(){return d},set:function(a){x&&((d=a)?g.bind(window,"unload",e):g.unbind(window,"unload",e),localStorage.setItem(document.location.href+".isLocal",a))}}});if(k.isUndefined(a.parent)){a.closed=
61−!1;g.addClass(this.domElement,m.CLASS_MAIN);g.makeSelectable(this.domElement,!1);if(x&&d){c.useLocalStorage=!0;var f=localStorage.getItem(document.location.href+".gui");f&&(a.load=JSON.parse(f))}this.__closeButton=document.createElement("div");this.__closeButton.innerHTML=m.TEXT_CLOSED;g.addClass(this.__closeButton,m.CLASS_CLOSE_BUTTON);this.domElement.appendChild(this.__closeButton);g.bind(this.__closeButton,"click",function(){c.closed=!c.closed})}else{void 0===a.closed&&(a.closed=!0);var r=document.createTextNode(a.name);
62−g.addClass(r,"controller-name");f=w(c,r);g.addClass(this.__ul,m.CLASS_CLOSED);g.addClass(f,"title");g.bind(f,"click",function(a){a.preventDefault();c.closed=!c.closed;return!1});a.closed||(this.closed=!1)}a.autoPlace&&(k.isUndefined(a.parent)&&(I&&(z=document.createElement("div"),g.addClass(z,"dg"),g.addClass(z,m.CLASS_AUTO_PLACE_CONTAINER),document.body.appendChild(z),I=!1),z.appendChild(this.domElement),g.addClass(this.domElement,m.CLASS_AUTO_PLACE)),this.parent||G(c,a.width));g.bind(window,"resize",
63−function(){c.onResize()});g.bind(this.__ul,"webkitTransitionEnd",function(){c.onResize()});g.bind(this.__ul,"transitionend",function(){c.onResize()});g.bind(this.__ul,"oTransitionEnd",function(){c.onResize()});this.onResize();a.resizable&&M(this);this.saveToLocalStorageIfPossible=e=function(){x&&"true"===localStorage.getItem(document.location.href+".isLocal")&&localStorage.setItem(document.location.href+".gui",JSON.stringify(c.getSaveObject()))};c.getRoot();a.parent||b()};m.toggleHide=function(){D=
64−!D;k.each(J,function(a){a.domElement.style.zIndex=D?-999:999;a.domElement.style.opacity=D?0:1})};m.CLASS_AUTO_PLACE="a";m.CLASS_AUTO_PLACE_CONTAINER="ac";m.CLASS_MAIN="main";m.CLASS_CONTROLLER_ROW="cr";m.CLASS_TOO_TALL="taller-than-window";m.CLASS_CLOSED="closed";m.CLASS_CLOSE_BUTTON="close-button";m.CLASS_DRAG="drag";m.DEFAULT_WIDTH=245;m.TEXT_CLOSED="Close Controls";m.TEXT_OPEN="Open Controls";g.bind(window,"keydown",function(a){"text"===document.activeElement.type||72!==a.which&&72!=a.keyCode||
65−m.toggleHide()},!1);k.extend(m.prototype,{add:function(a,b){return v(this,a,b,{factoryArgs:Array.prototype.slice.call(arguments,2)})},addColor:function(a,b){return v(this,a,b,{color:!0})},remove:function(a){this.__ul.removeChild(a.__li);this.__controllers.splice(this.__controllers.indexOf(a),1);var b=this;k.defer(function(){b.onResize()})},destroy:function(){this.autoPlace&&z.removeChild(this.domElement)},addFolder:function(a){if(void 0!==this.__folders[a])throw Error('You already have a folder in this GUI by the name "'+
66−a+'"');var b={name:a,parent:this};b.autoPlace=this.autoPlace;this.load&&this.load.folders&&this.load.folders[a]&&(b.closed=this.load.folders[a].closed,b.load=this.load.folders[a]);b=new m(b);this.__folders[a]=b;a=w(this,b.domElement);g.addClass(a,"folder");return b},open:function(){this.closed=!1},close:function(){this.closed=!0},onResize:function(){var a=this.getRoot();if(a.scrollable){var b=g.getOffset(a.__ul).top,c=0;k.each(a.__ul.childNodes,function(b){a.autoPlace&&b===a.__save_row||(c+=g.getHeight(b))});
67−window.innerHeight-b-20<c?(g.addClass(a.domElement,m.CLASS_TOO_TALL),a.__ul.style.height=window.innerHeight-b-20+"px"):(g.removeClass(a.domElement,m.CLASS_TOO_TALL),a.__ul.style.height="auto")}a.__resize_handle&&k.defer(function(){a.__resize_handle.style.height=a.__ul.offsetHeight+"px"});a.__closeButton&&(a.__closeButton.style.width=a.width+"px")},remember:function(){k.isUndefined(B)&&(B=new A,B.domElement.innerHTML=a);if(this.parent)throw Error("You can only call remember on a top level GUI.");var b=
68−this;k.each(Array.prototype.slice.call(arguments),function(a){0==b.__rememberedObjects.length&&L(b);-1==b.__rememberedObjects.indexOf(a)&&b.__rememberedObjects.push(a)});this.autoPlace&&G(this,this.width)},getRoot:function(){for(var a=this;a.parent;)a=a.parent;return a},getSaveObject:function(){var a=this.load;a.closed=this.closed;0<this.__rememberedObjects.length&&(a.preset=this.preset,a.remembered||(a.remembered={}),a.remembered[this.preset]=C(this));a.folders={};k.each(this.__folders,function(b,
69−c){a.folders[c]=b.getSaveObject()});return a},save:function(){this.load.remembered||(this.load.remembered={});this.load.remembered[this.preset]=C(this);E(this,!1);this.saveToLocalStorageIfPossible()},saveAs:function(a){this.load.remembered||(this.load.remembered={},this.load.remembered.Default=C(this,!0));this.load.remembered[a]=C(this);this.preset=a;F(this,a,!0);this.saveToLocalStorageIfPossible()},revert:function(a){k.each(this.__controllers,function(b){this.getRoot().load.remembered?y(a||this.getRoot(),
70−b):b.setValue(b.initialValue)},this);k.each(this.__folders,function(a){a.revert(a)});a||E(this.getRoot(),!1)},listen:function(a){var b=0==this.__listening.length;this.__listening.push(a);b&&H(this.__listening)}});return m}(dat.utils.css,'<div id="dg-save" class="dg dialogue">\n\n Here\'s the new load parameter for your <code>GUI</code>\'s constructor:\n\n <textarea id="dg-new-constructor"></textarea>\n\n <div id="dg-save-locally">\n\n <input id="dg-local-storage" type="checkbox"/> Automatically save\n values to <code>localStorage</code> on exit.\n\n <div id="dg-local-explain">The values saved to <code>localStorage</code> will\n override those passed to <code>dat.GUI</code>\'s constructor. This makes it\n easier to work incrementally, but <code>localStorage</code> is fragile,\n and your friends may not see the same values you do.\n \n </div>\n \n </div>\n\n</div>',
71−".dg {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row, <li> */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n",
72−dat.controllers.factory=function(f,a,d,e,c,b,p){return function(q,l,r,n){var u=q[l];if(p.isArray(r)||p.isObject(r))return new f(q,l,r);if(p.isNumber(u))return p.isNumber(r)&&p.isNumber(n)?new d(q,l,r,n):new a(q,l,{min:r,max:n});if(p.isString(u))return new e(q,l);if(p.isFunction(u))return new c(q,l,"");if(p.isBoolean(u))return new b(q,l)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(f,a,d){var e=
73−function(c,b){function d(){f.setValue(f.__input.value)}e.superclass.call(this,c,b);var f=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",d);a.bind(this.__input,"change",d);a.bind(this.__input,"blur",function(){f.__onFinishChange&&f.__onFinishChange.call(f,f.getValue())});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype,
74−f.prototype,{updateDisplay:function(){a.isActive(this.__input)||(this.__input.value=this.getValue());return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController,
75−dat.controllers.ColorController=function(f,a,d,e,c){function b(a,b,d,e){a.style.background="";c.each(l,function(c){a.style.cssText+="background: "+c+"linear-gradient("+b+", "+d+" 0%, "+e+" 100%); "})}function p(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";
76−a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var q=function(f,n){function u(b){v(b);a.bind(window,"mousemove",v);a.bind(window,
77−"mouseup",l)}function l(){a.unbind(window,"mousemove",v);a.unbind(window,"mouseup",l)}function g(){var a=e(this.value);!1!==a?(t.__color.__state=a,t.setValue(t.__color.toOriginal())):this.value=t.__color.toString()}function k(){a.unbind(window,"mousemove",w);a.unbind(window,"mouseup",k)}function v(b){b.preventDefault();var c=a.getWidth(t.__saturation_field),d=a.getOffset(t.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c;b=1-(b.clientY-d.top+document.body.scrollTop)/c;1<b?b=1:0>
78−b&&(b=0);1<e?e=1:0>e&&(e=0);t.__color.v=b;t.__color.s=e;t.setValue(t.__color.toOriginal());return!1}function w(b){b.preventDefault();var c=a.getHeight(t.__hue_field),d=a.getOffset(t.__hue_field);b=1-(b.clientY-d.top+document.body.scrollTop)/c;1<b?b=1:0>b&&(b=0);t.__color.h=360*b;t.setValue(t.__color.toOriginal());return!1}q.superclass.call(this,f,n);this.__color=new d(this.getValue());this.__temp=new d(0);var t=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,!1);
79−this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input=document.createElement("input");
80−this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){13===a.keyCode&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(b){a.addClass(this,"drag").bind(window,"mouseup",function(b){a.removeClass(t.__selector,"drag")})});var y=document.createElement("div");c.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});c.extend(this.__field_knob.style,
81−{position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(.5>this.__color.v?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});c.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});c.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});c.extend(y.style,{width:"100%",height:"100%",
82−background:"none"});b(y,"top","rgba(0,0,0,0)","#000");c.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});p(this.__hue_field);c.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",u);a.bind(this.__field_knob,"mousedown",u);a.bind(this.__hue_field,"mousedown",function(b){w(b);a.bind(window,
83−"mousemove",w);a.bind(window,"mouseup",k)});this.__saturation_field.appendChild(y);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};q.superclass=f;c.extend(q.prototype,f.prototype,{updateDisplay:function(){var a=e(this.getValue());if(!1!==a){var f=!1;
84−c.each(d.COMPONENTS,function(b){if(!c.isUndefined(a[b])&&!c.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return f=!0,{}},this);f&&c.extend(this.__color.__state,a)}c.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var l=.5>this.__color.v||.5<this.__color.s?255:0,p=255-l;c.extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toString(),border:this.__field_knob_border+"rgb("+l+
85−","+l+","+l+")"});this.__hue_knob.style.marginTop=100*(1-this.__color.h/360)+"px";this.__temp.s=1;this.__temp.v=1;b(this.__saturation_field,"left","#fff",this.__temp.toString());c.extend(this.__input.style,{backgroundColor:this.__input.value=this.__color.toString(),color:"rgb("+l+","+l+","+l+")",textShadow:this.__input_textShadow+"rgba("+p+","+p+","+p+",.7)"})}});var l=["-moz-","-o-","-webkit-","-ms-",""];return q}(dat.controllers.Controller,dat.dom.dom,dat.color.Color=function(f,a,d,e){function c(a,
86−b,c){Object.defineProperty(a,b,{get:function(){if("RGB"===this.__state.space)return this.__state[b];p(this,b,c);return this.__state[b]},set:function(a){"RGB"!==this.__state.space&&(p(this,b,c),this.__state.space="RGB");this.__state[b]=a}})}function b(a,b){Object.defineProperty(a,b,{get:function(){if("HSV"===this.__state.space)return this.__state[b];q(this);return this.__state[b]},set:function(a){"HSV"!==this.__state.space&&(q(this),this.__state.space="HSV");this.__state[b]=a}})}function p(b,c,d){if("HEX"===
87−b.__state.space)b.__state[c]=a.component_from_hex(b.__state.hex,d);else if("HSV"===b.__state.space)e.extend(b.__state,a.hsv_to_rgb(b.__state.h,b.__state.s,b.__state.v));else throw"Corrupted color state";}function q(b){var c=a.rgb_to_hsv(b.r,b.g,b.b);e.extend(b.__state,{s:c.s,v:c.v});e.isNaN(c.h)?e.isUndefined(b.__state.h)&&(b.__state.h=0):b.__state.h=c.h}var l=function(){this.__state=f.apply(this,arguments);if(!1===this.__state)throw"Failed to interpret color arguments";this.__state.a=this.__state.a||
88−1};l.COMPONENTS="r g b h s v hex a".split(" ");e.extend(l.prototype,{toString:function(){return d(this)},toOriginal:function(){return this.__state.conversion.write(this)}});c(l.prototype,"r",2);c(l.prototype,"g",1);c(l.prototype,"b",0);b(l.prototype,"h");b(l.prototype,"s");b(l.prototype,"v");Object.defineProperty(l.prototype,"a",{get:function(){return this.__state.a},set:function(a){this.__state.a=a}});Object.defineProperty(l.prototype,"hex",{get:function(){"HEX"!==!this.__state.space&&(this.__state.hex=
89−a.rgb_to_hex(this.r,this.g,this.b));return this.__state.hex},set:function(a){this.__state.space="HEX";this.__state.hex=a}});return l}(dat.color.interpret,dat.color.math=function(){var f;return{hsv_to_rgb:function(a,d,e){var c=a/60-Math.floor(a/60),b=e*(1-d),f=e*(1-c*d);d=e*(1-(1-c)*d);a=[[e,d,b],[f,e,b],[b,e,d],[b,f,e],[d,b,e],[e,b,f]][Math.floor(a/60)%6];return{r:255*a[0],g:255*a[1],b:255*a[2]}},rgb_to_hsv:function(a,d,e){var c=Math.min(a,d,e),b=Math.max(a,d,e),c=b-c;if(0==b)return{h:NaN,s:0,v:0};
90−a=(a==b?(d-e)/c:d==b?2+(e-a)/c:4+(a-d)/c)/6;0>a&&(a+=1);return{h:360*a,s:c/b,v:b/255}},rgb_to_hex:function(a,d,e){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,d);return a=this.hex_with_component(a,0,e)},component_from_hex:function(a,d){return a>>8*d&255},hex_with_component:function(a,d,e){return e<<(f=8*d)|a&~(255<<f)}}}(),dat.color.toString,dat.utils.common),dat.color.interpret,dat.utils.common),dat.utils.requestAnimationFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||
91−window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(f,a){window.setTimeout(f,1E3/60)}}(),dat.dom.CenteredDiv=function(f,a){var d=function(){this.backgroundElement=document.createElement("div");a.extend(this.backgroundElement.style,{backgroundColor:"rgba(0,0,0,0.8)",top:0,left:0,display:"none",zIndex:"1000",opacity:0,WebkitTransition:"opacity 0.2s linear",transition:"opacity 0.2s linear"});f.makeFullscreen(this.backgroundElement);this.backgroundElement.style.position=
92−"fixed";this.domElement=document.createElement("div");a.extend(this.domElement.style,{position:"fixed",display:"none",zIndex:"1001",opacity:0,WebkitTransition:"-webkit-transform 0.2s ease-out, opacity 0.2s linear",transition:"transform 0.2s ease-out, opacity 0.2s linear"});document.body.appendChild(this.backgroundElement);document.body.appendChild(this.domElement);var d=this;f.bind(this.backgroundElement,"click",function(){d.hide()})};d.prototype.show=function(){var d=this;this.backgroundElement.style.display=
93−"block";this.domElement.style.display="block";this.domElement.style.opacity=0;this.domElement.style.webkitTransform="scale(1.1)";this.layout();a.defer(function(){d.backgroundElement.style.opacity=1;d.domElement.style.opacity=1;d.domElement.style.webkitTransform="scale(1)"})};d.prototype.hide=function(){var a=this,c=function(){a.domElement.style.display="none";a.backgroundElement.style.display="none";f.unbind(a.domElement,"webkitTransitionEnd",c);f.unbind(a.domElement,"transitionend",c);f.unbind(a.domElement,
94−"oTransitionEnd",c)};f.bind(this.domElement,"webkitTransitionEnd",c);f.bind(this.domElement,"transitionend",c);f.bind(this.domElement,"oTransitionEnd",c);this.backgroundElement.style.opacity=0;this.domElement.style.opacity=0;this.domElement.style.webkitTransform="scale(1.1)"};d.prototype.layout=function(){this.domElement.style.left=window.innerWidth/2-f.getWidth(this.domElement)/2+"px";this.domElement.style.top=window.innerHeight/2-f.getHeight(this.domElement)/2+"px"};return d}(dat.dom.dom,dat.utils.common),
95−dat.dom.dom,dat.utils.common);
scripts/system/particle_explorer/hifi-entity-ui.js+680−0
@@ -0,0 +1,680 @@
1+/* global window, document, print, alert, console,setTimeout, clearTimeout, _ $ */
2+/* eslint no-console: 0 */
3+
4+/**
5+UI Builder V1.0
6+
7+Created by Matti 'Menithal' Lahtinen
8+24/5/2017
9+Copyright 2017 High Fidelity, Inc.
10+
11+This can eventually be expanded to all of Edit, for now, starting
12+with Particles Only.
13+
14+This is created for the sole purpose of streamliming the bridge, and to simplify
15+the logic between an inputfield in WebView and Entities in High Fidelity.
16+
17+We also do not need anything as heavy as jquery or any other platform,
18+as we are mostly only building for QT (while, all the other JS frameworks usually do alot of polyfilling)
19+
20+Available Types:
21+
22+ JSONInputField - Accepts JSON input, once one presses Save, it will be propegated.
23+ Button- A Button that listens for a custom event as defined by callback
24+ Boolean - Creates a checkbox that the user can either check or uncheck
25+ SliderFloat - Creates a slider (with input) that has Float values from min to max.
26+ Default is min 0, max 1
27+ SliderInteger - Creates a slider (with input) that has a Integer value from min to max.
28+ Default is min 1, max 10000
29+ SliderRadian - Creates a slider (with input) that has Float values in degrees,
30+ that are converted to radians. default is min 0, max Math.PI.
31+ Texture - Creates a Image with an url input field that points to texture.
32+ If image cannot form, show "cannot find image"
33+ VecQuaternion - Creates a 3D Vector field that converts to quaternions.
34+ Checkbox exists to show quaternions instead.
35+ Color - Create field color button, that when pressed, opens the color picker.
36+ Vector - Create a 3D Vector field that has one to one correspondence.
37+
38+The script will use this structure to build a UI that is connected The
39+id fields within High Fidelity
40+
41+This should make editing, and everything related much more simpler to maintain,
42+and If there is any changes to either the Entities or properties of
43+
44+**/
45+
46+var RADIANS_PER_DEGREE = Math.PI / 180;
47+
48+var roundFloat = function (input, round) {
49+ round = round ? round : 1000;
50+ var sanitizedInput;
51+ if (typeof input === "string") {
52+ sanitizedInput = parseFloat(input);
53+ } else {
54+ sanitizedInput = input;
55+ }
56+ return Math.round(sanitizedInput * round) / round;
57+};
58+
59+function HifiEntityUI(parent) {
60+ this.parent = parent;
61+
62+ var self = this;
63+ this.webBridgeSync = _.debounce(function (id, val) {
64+ if (self.EventBridge) {
65+ var sendPackage = {};
66+ sendPackage[id] = val;
67+ self.submitChanges(sendPackage);
68+ }
69+ }, 125);
70+}
71+
72+HifiEntityUI.prototype = {
73+ setOnSelect: function (callback) {
74+ this.onSelect = callback;
75+ },
76+ submitChanges: function (structure) {
77+ var message = {
78+ messageType: "settings_update",
79+ updatedSettings: structure
80+ };
81+ this.EventBridge.emitWebEvent(JSON.stringify(message));
82+ },
83+ setUI: function (structure) {
84+ this.structure = structure;
85+ },
86+ disableFields: function () {
87+ var fields = document.getElementsByTagName("input");
88+ for (var i = 0; i < fields.length; i++) {
89+ if (fields[i].getAttribute("type") !== "button") {
90+ fields[i].value = "";
91+ }
92+
93+ fields[i].setAttribute("disabled", true);
94+ }
95+ var textures = document.getElementsByTagName("img");
96+ for (i = 0; i < textures.length; i++) {
97+ textures[i].src = "";
98+ }
99+
100+ textures = document.getElementsByClassName("with-texture");
101+ for (i = 0; i < textures.length; i++) {
102+ textures[i].classList.remove("with-textures");
103+ textures[i].classList.add("no-texture");
104+ }
105+
106+ var textareas = document.getElementsByTagName("textarea");
107+ for (var x = 0; x < textareas.length; x++) {
108+ textareas[x].remove();
109+ }
110+ },
111+ getSettings: function () {
112+ var self = this;
113+ var json = {};
114+ var keys = Object.keys(self.builtRows);
115+
116+ for (var i = 0; i < keys.length; i++) {
117+ var key = keys[i];
118+ var el = self.builtRows[key];
119+ if (el.className.indexOf("checkbox") !== -1) {
120+ json[key] = document.getElementById(key)
121+ .checked ? true : false;
122+ } else if (el.className.indexOf("vector-section") !== -1) {
123+ var vector = {};
124+ if (el.className.indexOf("rgb") !== -1) {
125+ var red = document.getElementById(key + "-red");
126+ var blue = document.getElementById(key + "-blue");
127+ var green = document.getElementById(key + "-green");
128+ vector.red = red.value;
129+ vector.blue = blue.value;
130+ vector.green = green.value;
131+ } else if (el.className.indexOf("pyr") !== -1) {
132+ var p = document.getElementById(key + "-Pitch");
133+ var y = document.getElementById(key + "-Yaw");
134+ var r = document.getElementById(key + "-Roll");
135+ vector.x = p.value;
136+ vector.y = y.value;
137+ vector.z = r.value;
138+ } else {
139+ var x = document.getElementById(key + "-x");
140+ var ey = document.getElementById(key + "-y");
141+ var z = document.getElementById(key + "-z");
142+ vector.x = x.value;
143+ vector.y = ey.value;
144+ vector.z = z.value;
145+ }
146+ json[key] = vector;
147+ } else if (el.className.length > 0) {
148+ json[key] = document.getElementById(key)
149+ .value;
150+ }
151+ }
152+
153+ return json;
154+ },
155+ fillFields: function (currentProperties) {
156+ var self = this;
157+ var fields = document.getElementsByTagName("input");
158+
159+ if (!currentProperties.locked) {
160+ for (var i = 0; i < fields.length; i++) {
161+ fields[i].removeAttribute("disabled");
162+ }
163+ }
164+ if (self.onSelect) {
165+ self.onSelect();
166+ }
167+ var keys = Object.keys(currentProperties);
168+
169+
170+ for (var e in keys) {
171+ if (keys.hasOwnProperty(e)) {
172+ var value = keys[e];
173+
174+ var property = currentProperties[value];
175+ var field = self.builtRows[value];
176+ if (field) {
177+ var el = document.getElementById(value);
178+
179+ if (field.className.indexOf("radian") !== -1) {
180+ el.value = property / RADIANS_PER_DEGREE;
181+ el.onchange({
182+ target: el
183+ });
184+ } else if (field.className.indexOf("range") !== -1 || field.className.indexOf("texture") !== -1) {
185+ el.value = property;
186+ el.onchange({
187+ target: el
188+ });
189+ } else if (field.className.indexOf("checkbox") !== -1) {
190+ if (property) {
191+ el.setAttribute("checked", property);
192+ } else {
193+ el.removeAttribute("checked");
194+ }
195+ } else if (field.className.indexOf("vector-section") !== -1) {
196+ if (field.className.indexOf("rgb") !== -1) {
197+ var red = document.getElementById(value + "-red");
198+ var blue = document.getElementById(value + "-blue");
199+ var green = document.getElementById(value + "-green");
200+ red.value = parseInt(property.red);
201+ blue.value = parseInt(property.blue);
202+ green.value = parseInt(property.green);
203+
204+ red.oninput({
205+ target: red
206+ });
207+ } else if (field.className.indexOf("xyz") !== -1) {
208+ var x = document.getElementById(value + "-x");
209+ var y = document.getElementById(value + "-y");
210+ var z = document.getElementById(value + "-z");
211+
212+ x.value = roundFloat(property.x, 100);
213+ y.value = roundFloat(property.y, 100);
214+ z.value = roundFloat(property.z, 100);
215+ } else if (field.className.indexOf("pyr") !== -1) {
216+ var pitch = document.getElementById(value + "-Pitch");
217+ var yaw = document.getElementById(value + "-Yaw");
218+ var roll = document.getElementById(value + "-Roll");
219+
220+ pitch.value = roundFloat(property.x, 100);
221+ yaw.value = roundFloat(property.y, 100);
222+ roll.value = roundFloat(property.z, 100);
223+
224+ }
225+ }
226+ }
227+ }
228+ }
229+ },
230+ connect: function (EventBridge) {
231+ this.EventBridge = EventBridge;
232+
233+ var self = this;
234+
235+ EventBridge.emitWebEvent(JSON.stringify({
236+ messageType: 'page_loaded'
237+ }));
238+
239+ EventBridge.scriptEventReceived.connect(function (data) {
240+ data = JSON.parse(data);
241+
242+ if (data.messageType === 'particle_settings') {
243+ // Update settings
244+ var currentProperties = data.currentProperties;
245+ self.fillFields(currentProperties);
246+ // Do expected property match with structure;
247+ } else if (data.messageType === 'particle_close') {
248+ self.disableFields();
249+ }
250+ });
251+ },
252+ build: function () {
253+ var self = this;
254+ var sections = Object.keys(this.structure);
255+ this.builtRows = {};
256+ sections.forEach(function (section, index) {
257+ var properties = self.structure[section];
258+ self.addSection(self.parent, section, properties, index);
259+ });
260+ },
261+ addSection: function (parent, section, properties, index) {
262+ var self = this;
263+
264+ var sectionDivHeader = document.createElement("div");
265+ var title = document.createElement("label");
266+ var dropDown = document.createElement("span");
267+
268+ dropDown.className = "arrow";
269+ sectionDivHeader.className = "section-header";
270+ title.innerHTML = section;
271+ sectionDivHeader.appendChild(title);
272+ sectionDivHeader.appendChild(dropDown);
273+ var collapsed = index !== 0;
274+
275+ dropDown.innerHTML = collapsed ? "L" : "M";
276+ sectionDivHeader.setAttribute("collapsed", collapsed);
277+ parent.appendChild(sectionDivHeader);
278+
279+ var sectionDivBody = document.createElement("div");
280+ sectionDivBody.className = "property-group";
281+
282+ var animationWrapper = document.createElement("div");
283+ animationWrapper.className = "section-wrap";
284+
285+ for (var property in properties) {
286+ if (properties.hasOwnProperty(property)) {
287+ var builtRow = self.addElement(animationWrapper, properties[property]);
288+ var id = properties[property].id;
289+ if (id) {
290+ self.builtRows[id] = builtRow;
291+ }
292+ }
293+ }
294+ sectionDivBody.appendChild(animationWrapper);
295+ parent.appendChild(sectionDivBody);
296+ _.defer(function () {
297+ var height = (animationWrapper.clientHeight) + "px";
298+ if (collapsed) {
299+ sectionDivBody.classList.remove("visible");
300+ sectionDivBody.style.maxHeight = "0px";
301+ } else {
302+ sectionDivBody.classList.add("visible");
303+ sectionDivBody.style.maxHeight = height;
304+ }
305+
306+ sectionDivHeader.onclick = function () {
307+ collapsed = !collapsed;
308+ if (collapsed) {
309+ sectionDivBody.classList.remove("visible");
310+ sectionDivBody.style.maxHeight = "0px";
311+ } else {
312+ sectionDivBody.classList.add("visible");
313+ sectionDivBody.style.maxHeight = (animationWrapper.clientHeight) + "px";
314+ }
315+ // sectionDivBody.style.display = collapsed ? "none": "block";
316+ dropDown.innerHTML = collapsed ? "L" : "M";
317+ sectionDivHeader.setAttribute("collapsed", collapsed);
318+ };
319+ });
320+ },
321+ addLabel: function (parent, group) {
322+ var label = document.createElement("label");
323+ label.innerHTML = group.name;
324+ parent.appendChild(label);
325+ if (group.unit) {
326+ var span = document.createElement("span");
327+ span.innerHTML = group.unit;
328+ span.className = "unit";
329+ label.appendChild(span);
330+ }
331+ return label;
332+ },
333+ addVector: function (parent, group, labels, domArray) {
334+ var self = this;
335+ var inputs = labels ? labels : ["x", "y", "z"];
336+ domArray = domArray ? domArray : [];
337+ parent.id = group.id;
338+ for (var index in inputs) {
339+ var element = document.createElement("input");
340+
341+ element.setAttribute("type", "number");
342+ element.className = inputs[index];
343+ element.id = group.id + "-" + inputs[index];
344+
345+ if (group.defaultRange) {
346+ if (group.defaultRange.min) {
347+ element.setAttribute("min", group.defaultRange.min);
348+ }
349+ if (group.defaultRange.max) {
350+ element.setAttribute("max", group.defaultRange.max);
351+ }
352+ if (group.defaultRange.step) {
353+ element.setAttribute("step", group.defaultRange.step);
354+ }
355+ }
356+ if (group.oninput) {
357+ element.oninput = group.oninput;
358+ } else {
359+ element.oninput = function (event) {
360+ self.webBridgeSync(group.id, {
361+ x: domArray[0].value,
362+ y: domArray[1].value,
363+ z: domArray[2].value
364+ });
365+ };
366+ }
367+ element.onchange = element.oninput;
368+ domArray.push(element);
369+ }
370+
371+ this.addLabel(parent, group);
372+ var className = "";
373+ for (var i = 0; i < inputs.length; i++) {
374+ className += inputs[i].charAt(0)
375+ .toLowerCase();
376+ }
377+ parent.className += " property vector-section " + className;
378+
379+ // Add Tuple and the rest
380+ var tupleContainer = document.createElement("div");
381+ tupleContainer.className = "tuple";
382+ for (var domIndex in domArray) {
383+ var container = domArray[domIndex];
384+ var div = document.createElement("div");
385+ var label = document.createElement("label");
386+ label.innerHTML = inputs[domIndex] + ":";
387+ label.setAttribute("for", container.id);
388+ div.appendChild(container);
389+ div.appendChild(label);
390+ tupleContainer.appendChild(div);
391+ }
392+ parent.appendChild(tupleContainer);
393+ },
394+ addVectorQuaternion: function (parent, group) {
395+ this.addVector(parent, group, ["Pitch", "Yaw", "Roll"]);
396+ },
397+ addColorPicker: function (parent, group) {
398+ var self = this;
399+ var $colPickContainer = $('<div>', {
400+ id: group.id,
401+ class: "color-picker"
402+ });
403+ var updateColors = function (red, green, blue) {
404+ $colPickContainer.css('background-color', "rgb(" +
405+ red + "," +
406+ green + "," +
407+ blue + ")");
408+ };
409+
410+ var inputs = ["red", "green", "blue"];
411+ var domArray = [];
412+ group.oninput = function (event) {
413+ $colPickContainer.colpickSetColor(
414+ {
415+ r: domArray[0].value,
416+ g: domArray[1].value,
417+ b: domArray[2].value
418+ },
419+ true);
420+ };
421+ group.defaultRange = {
422+ min: 0,
423+ max: 255,
424+ step: 1
425+ };
426+
427+ parent.appendChild($colPickContainer[0]);
428+ self.addVector(parent, group, inputs, domArray);
429+
430+ updateColors(domArray[0].value, domArray[1].value, domArray[2].value);
431+
432+ // Could probably write a custom one for this to completely write out jquery,
433+ // but for now, using the same as earlier.
434+
435+ /* Color Picker Logic Here */
436+
437+
438+ $colPickContainer.colpick({
439+ colorScheme: 'dark',
440+ layout: 'hex',
441+ color: {
442+ r: domArray[0].value,
443+ g: domArray[1].value,
444+ b: domArray[2].value
445+ },
446+ onChange: function (hsb, hex, rgb, el) {
447+ updateColors(rgb.r, rgb.g, rgb.b);
448+
449+ domArray[0].value = rgb.r;
450+ domArray[1].value = rgb.g;
451+ domArray[2].value = rgb.b;
452+ self.webBridgeSync(group.id, {
453+ red: rgb.r,
454+ green: rgb.g,
455+ blue: rgb.b
456+ });
457+ },
458+ onSubmit: function (hsb, hex, rgb, el) {
459+ $(el)
460+ .css('background-color', '#' + hex);
461+ $(el)
462+ .colpickHide();
463+ domArray[0].value = rgb.r;
464+ domArray[1].value = rgb.g;
465+ domArray[2].value = rgb.b;
466+ self.webBridgeSync(group.id, {
467+ red: rgb.r,
468+ green: rgb.g,
469+ blue: rgb.b
470+ });
471+ }
472+ });
473+ },
474+ addTextureField: function (parent, group) {
475+ var self = this;
476+ this.addLabel(parent, group);
477+ parent.className += " property texture";
478+ var textureImage = document.createElement("div");
479+ var textureUrl = document.createElement("input");
480+ textureUrl.setAttribute("type", "text");
481+ textureUrl.id = group.id;
482+ textureImage.className = "texture-image no-texture";
483+ var image = document.createElement("img");
484+ var imageLoad = _.debounce(function (url) {
485+ if (url.length > 0) {
486+ textureImage.classList.remove("no-texture");
487+ textureImage.classList.add("with-texture");
488+ image.src = url;
489+ image.style.display = "block";
490+ } else {
491+ image.src = "";
492+ image.style.display = "none";
493+ textureImage.classList.add("no-texture");
494+ }
495+ self.webBridgeSync(group.id, url);
496+ }, 250);
497+
498+ textureUrl.oninput = function (event) {
499+ // Add throttle
500+ var url = event.target.value;
501+ imageLoad(url);
502+ };
503+ textureUrl.onchange = textureUrl.oninput;
504+ textureImage.appendChild(image);
505+ parent.appendChild(textureImage);
506+ parent.appendChild(textureUrl);
507+ },
508+ addSlider: function (parent, group) {
509+ var self = this;
510+ this.addLabel(parent, group);
511+ parent.className += " property range";
512+ var container = document.createElement("div");
513+ container.className = "slider-wrapper";
514+ var slider = document.createElement("input");
515+ slider.setAttribute("type", "range");
516+
517+ var inputField = document.createElement("input");
518+ inputField.setAttribute("type", "number");
519+
520+ container.appendChild(slider);
521+ container.appendChild(inputField);
522+ parent.appendChild(container);
523+
524+ if (group.type === "SliderInteger") {
525+ inputField.setAttribute("min", group.min !== undefined ? group.min : 0);
526+ inputField.setAttribute("step", 1);
527+
528+ slider.setAttribute("min", group.min !== undefined ? group.min : 0);
529+ slider.setAttribute("max", group.max !== undefined ? group.max : 10000);
530+ slider.setAttribute("step", 1);
531+
532+ inputField.oninput = function (event) {
533+
534+ if (parseInt(event.target.value) > parseInt(slider.getAttribute("max")) && group.max !== 1) {
535+ slider.setAttribute("max", event.target.value);
536+ }
537+ slider.value = event.target.value;
538+
539+ self.webBridgeSync(group.id, slider.value);
540+ };
541+ inputField.onchange = inputField.oninput;
542+ slider.oninput = function (event) {
543+ inputField.value = event.target.value;
544+ self.webBridgeSync(group.id, slider.value);
545+ };
546+
547+ inputField.id = group.id;
548+ } else if (group.type === "SliderRadian") {
549+ slider.setAttribute("min", group.min !== undefined ? group.min : 0);
550+ slider.setAttribute("max", group.max !== undefined ? group.max : 180);
551+ slider.setAttribute("step", 1);
552+ parent.className += " radian";
553+ inputField.setAttribute("min", (group.min !== undefined ? group.min : 0));
554+ inputField.setAttribute("max", (group.max !== undefined ? group.max : 180));
555+
556+ inputField.oninput = function (event) {
557+ slider.value = event.target.value;
558+ self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE);
559+ };
560+ inputField.onchange = inputField.oninput;
561+
562+ inputField.id = group.id;
563+ slider.oninput = function (event) {
564+ if (event.target.value > 0) {
565+ inputField.value = Math.floor(event.target.value);
566+ } else {
567+ inputField.value = Math.ceil(event.target.value);
568+ }
569+ self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE);
570+ };
571+ var degrees = document.createElement("label");
572+ degrees.innerHTML = "&#176;";
573+ degrees.style.fontSize = "1.4rem";
574+ degrees.style.display = "inline";
575+ degrees.style.verticalAlign = "top";
576+ degrees.style.paddingLeft = "0.4rem";
577+ container.appendChild(degrees);
578+
579+ } else {
580+ // Must then be Float
581+ inputField.setAttribute("min", group.min !== undefined ? group.min : 0);
582+ slider.setAttribute("step", 0.01);
583+
584+ slider.setAttribute("min", group.min !== undefined ? group.min : 0);
585+ slider.setAttribute("max", group.max !== undefined ? group.max : 1);
586+ slider.setAttribute("step", 0.01);
587+
588+ inputField.oninput = function (event) {
589+ if (parseFloat(event.target.value) > parseFloat(slider.getAttribute("max")) && group.max !== 1) {
590+ slider.setAttribute("max", event.target.value);
591+ }
592+
593+ slider.value = event.target.value;
594+ self.webBridgeSync(group.id, slider.value);
595+ // bind web sock update here.
596+ };
597+ inputField.onchange = inputField.oninput;
598+ slider.oninput = function (event) {
599+ inputField.value = event.target.value;
600+ self.webBridgeSync(group.id, inputField.value);
601+ };
602+
603+ inputField.id = group.id;
604+ }
605+
606+ // UpdateBinding
607+ },
608+ addCheckBox: function (parent, group) {
609+ var checkBox = document.createElement("input");
610+ checkBox.setAttribute("type", "checkbox");
611+ var self = this;
612+ checkBox.onchange = function (event) {
613+ self.webBridgeSync(group.id, event.target.checked);
614+ };
615+ checkBox.id = group.id;
616+ parent.appendChild(checkBox);
617+ var label = this.addLabel(parent, group);
618+ label.setAttribute("for", checkBox.id);
619+ parent.className += " property checkbox";
620+ },
621+ addElement: function (parent, group) {
622+ var self = this;
623+ var property = document.createElement("div");
624+ property.id = group.id;
625+
626+ var row = document.createElement("div");
627+ switch (group.type) {
628+ case "Button":
629+ var button = document.createElement("input");
630+ button.setAttribute("type", "button");
631+ button.id = group.id;
632+ if (group.disabled) {
633+ button.disabled = group.disabled;
634+ }
635+ button.className = group.class;
636+ button.value = group.name;
637+
638+ button.onclick = group.callback;
639+ parent.appendChild(button);
640+ break;
641+ case "Row":
642+ var hr = document.createElement("hr");
643+ hr.className = "splitter";
644+ if (group.id) {
645+ hr.id = group.id;
646+ }
647+ parent.appendChild(hr);
648+ break;
649+ case "Boolean":
650+ self.addCheckBox(row, group);
651+ parent.appendChild(row);
652+ break;
653+ case "SliderFloat":
654+ case "SliderInteger":
655+ case "SliderRadian":
656+ self.addSlider(row, group);
657+ parent.appendChild(row);
658+ break;
659+ case "Texture":
660+ self.addTextureField(row, group);
661+ parent.appendChild(row);
662+ break;
663+ case "Color":
664+ self.addColorPicker(row, group);
665+ parent.appendChild(row);
666+ break;
667+ case "Vector":
668+ self.addVector(row, group);
669+ parent.appendChild(row);
670+ break;
671+ case "VectorQuaternion":
672+ self.addVectorQuaternion(row, group);
673+ parent.appendChild(row);
674+ break;
675+ default:
676+ console.log("not defined");
677+ }
678+ return row;
679+ }
680+};
scripts/system/particle_explorer/particle-style.css+124−0
@@ -0,0 +1,124 @@
1+/*
2+// particle-style.css
3+//
4+// Created by Matti 'Menithal' Lahtinen on 21 May 2017
5+// Copyright 2017 High Fidelity, Inc.
6+//
7+// Distributed under the Apache License, Version 2.0.
8+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
9+*/
10+
11+
12+.property-group {
13+ max-height: 0;
14+ -webkit-transition: max-height 0.15s ease-out;
15+ transition: max-height 0.15s ease-out;
16+ overflow: hidden;
17+}
18+.property-group.visible {
19+ transition: max-height 0.25s ease-in;
20+}
21+.section-wrap {
22+ width: 100%;
23+}
24+.property {
25+ padding: 0.4rem 0;
26+ margin: 0;
27+}
28+.property.checkbox {
29+ margin: 0;
30+}
31+.property.range label{
32+ display: block;
33+}
34+
35+input[type="button"] {
36+ margin: 0.4rem;
37+ min-width: 6rem;
38+}
39+input[type="text"] {
40+ margin: 0;
41+}
42+.property.range input[type=number]{
43+ margin-left: 0.8rem;
44+ width: 5.4rem;
45+ height: 1.8rem;
46+}
47+input[type=range] {
48+ -webkit-appearance: none;
49+ background: #2e2e2e;
50+ height: 1.8rem;
51+ border-radius: 1rem;
52+}
53+input[type=range]::-webkit-slider-thumb {
54+ -webkit-appearance:none;
55+ width: 0.6rem;
56+ height: 1.8rem;
57+ padding:0;
58+ margin: 0;
59+ background-color: #696969;
60+ border-radius: 1rem;
61+}
62+input[type=range]::-webkit-slider-thumb:hover {
63+ background-color: white;
64+}
65+input[type=range]:focus { /*#252525*/
66+ outline: none;
67+}
68+.tuple label {
69+ text-transform: capitalize;
70+}
71+.slider-wrapper {
72+ display: table;
73+ padding: 0.4rem 0;
74+}
75+hr.splitter{
76+ width: 100%;
77+ padding: 0.2rem 0 0 0;
78+ margin: 0;
79+ position: relative;
80+ clear: both;
81+}
82+hr.splitter:last-of-type{
83+ padding:0;
84+}
85+#rem {
86+ height: 1rem;
87+ width: 1rem;
88+}
89+.property {
90+ min-height: 2rem;
91+}
92+.property.vector-section{
93+
94+ width: 24rem;
95+}
96+
97+.property.texture {
98+ display: block;
99+}
100+.property.texture input{
101+ margin: 0.4rem 0;
102+}
103+.texture-image img{
104+ padding: 0;
105+ margin: 0;
106+ width: 100%;
107+ height: 100%;
108+ display: none;
109+}
110+.texture-image {
111+ display: block;
112+ position: relative;
113+ background-repeat: no-repeat;
114+ background-position: center;
115+ background-size: 100% 100%;
116+ margin-top: 0.4rem;
117+ height:128px;
118+ width: 128px;
119+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABhNJREFUeNrsnVFy4joQRVsSCwAqBMwqsrRsIavMEkICoeAf2+8j1R5ZGDBgpLzoUDVVmTT2dc8It/paOpi3t7faOSciImVZyn6/l6qqRETEWivj8VistYPFd7ud1HUtIiLGGBmPx5JKX0RkMplIzvmPnHNijBERaS7Ef1lrB40bY1oXgH5a/ZH+8P7+LlVVycfHR/MGa60URdGcYOi4MUaKomhGaGx9EZHlcplMP2X+Ly8vPwOgLEtxzklVVVJVVfOznqAsy9YFXhuvqqq5AF/Lj+srtr7+LpV+yvz1mNF+vxcRkdVqJdZaeXp6ap1ws9m0TjibzVoj6lJ8vV6fjJdlKev1ujViU+j7t8tc8p9Op1KWpYw06L9JL0Av0r9l+jXl3nhd11JV1VE8tn5YM3PI3xjzoxVOGvyDU7zQj6s/0tGmI++amtNV087F9Wf/FnVPzRtCXz8RdV1nlb/efUbaJy4Wi0FqzjU1yRgjs9ls0Jp3jb6IyPPzczL9lPkvFot/dwCtB/om/x9oyJoXxps65NW8mPpdNTeX/JtBEtYE/+AUL/Tj6g/qA3TVnD41a6g++Bp9rYOp9FPnH80HOBcvy1I2m81D++BL+o/2AX5r/vgA+AD4AOif8AH8EdpVcy71sX3jWp/8W2AKff/TkUv+Oufr9AF0YuKc66xJ18T7eNP3nP9WfZ0EzufzJPqp8y+KQuq67vYBdETqCDpVU/rEw5oUnr+rD46h73/qUuinzh8fAP22D6AjxznXcqq6akrf+KmaFB6vf4+t7/sAelfIJf/GB9jtdmKMkdVq1dQM3zg4VVNU/NY+1Bgjh8Oh6YM1+dj6X19fzXwgp/wbH0DFtS7oyf0RdKqmhPFr+1RdseKfP7a+Px/IKX98APTbPoDOJrv60L417d54TH3V8lfS5pT/yfUA6/X6qOZcqkm3xrUm6X9CTH3fB0ihnzr/Ix9A/3T1qbfWpGvjMfX9T0UK/dT54wOg/88H8EfGPTVr6D740frhLDmn/Hv5AH1qku9t31KTzh3/aP1LPsBfzr+XDxCO0K6ack/N6qp5MfUv+QB/Of/ePsCQfWmfc6EfV3/kjzZrrRwOh9YtKHSm/LjOH3yrMTzej4c1y//51PHoP0a/tR7AOSdFURw9rz5VU049zw7jl2qWrosP++BY+iI/+wJS6afMv9kXoA6gvimsieHzZr/m6MTp3PPuc3G9SP95OPpx9JtOgT4cHwA+QCJ9+ADwAeADsC+AfQHo/4b1APAB4APAB4APAB8APgB9OD4AfAD4AFFqEnwA+AD4APgA6P86HwA+AHyAZhIBHwA+AHwA+AD04X/eB4APAB8APgB8APgA8AHow/P0AeADwAeADwAfAD4AfAD68Px8APgA8AHgA8AHgA8AH0DO70/v6lHvjaOfVn8U/iLcXx5OUML96X49vRTX3/nPw9FPo9+sB5hMJuKck+VyeVRTrLWtdfNdcf95eldNCuOfn5+tSYy/Pz+2voi0fICc8p/P5z93gJAPEN4+wufN4evaePj99eH+ePTj6p/1Abp60kt9Ksf/v46HDwAfAD6A/6gUPgD7AtgXwPP4DNcDwAeADwAfAD4AfAD4ADyPz289AHyA+Pqp84cPIPAB8AHwAfAB8AHgA7Q+HfAB4APAB4APAB+APjw3HwA+AHwA+ADwAeADwAegD8/TB4APAB8APgB8APgA8AHow/PzAeADwAeADwAfAD4AfACJ//316KfVH/mjLeb31+vx/kWhH0+/tR7AOSdFUUT9/nq9oK4+OJa+iLT25+eUf7MvIOQDxPr+en2F++PRj6PfdAr04fgA8AES6cMHgA8AH4B9AewLQP83rAeADwAfAD4AfAD4APAB6MPxAeADwAeIUpPgA8AHwAfAB0D/1/kA8AHgAzSTCPgA8AHgA8AHoA//8z4AfAD4APAB4APAB4APQB+epw8AHwA+AHwA+ADwAeAD0Ifn5wPAB4APAB8APgB8gBz5AOb19bX2TYLpdNpqQ7bbbctJGjJeVZVst9vWLSu2/vf3t+Sc/yicFIRr0C7Fu76f/lw8XBePflr9/wYAqWwWUSLcO54AAAAASUVORK5CYII=');
120+}
121+
122+.texture-image.no-texture{
123+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAB81JREFUeNrsnTGPm0oXht97FWm2Ch2pTEeHpUihsyvTuXO67Ta/IPkr+Qfp3MWdO7Zad0SKZDo6XIWOrTzV9xVXZ8SygGHXG4/t96lW68GGw8vMmZlzDv98+/btfyBXy780wXXzTv74/fs3rXFFfPz4kT0AoQAoAJqAAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQCgAQgEQCoBQAIQCIBQAoQAIBUAoAHLmvDv3C7i7u4PjOMiyDOv1+mC75XKJoiga2wRBAN/34TgOHMdBWZYoigJpmiLPcwrARhzHAQD4vg/P81pvlLRrwvM8zGYz00ZrbY5xHAe+7yPPc9zf36MsSwrAVmazGX78+DHoGM/zsFgsAAB5nmOz2ZgeQimF8XiMMAxNu+VyaQRCH8Ai8jyH4zgIw7D3MUopzOdzAECaplitVk+GB601kiTBz58/obWG4ziIoohOoI38+vULABCGYWd3X2U6nUIphbIsEcdxa7uiKPDw8GCGGtd1KQDbKMsSWZZBKYXJZNLrGN/3zdN/iDRNTdcvx1EAFqGUwmazeeIQduG6LpRSAIAsy3r9hrRjD2BxL5AkiXEI+8wetNa9PXtp13eIoQBOQJIkxmHrcgjlJkov8JKpJwVgIVpr47CFYdh6g/f7/ZM5/9CehgKwmDRNURQFlFKYTqeNN/rx8dH0AH2faBn7KYAzQKZ1QRCYZd0qf/78MX+PRqNe3ymO5W63owBsR9bwZShoGirEq++zeBQEweBZAwVwYh4eHqC1RhAErQ6jOHVdK3yu65qhJE1TDgHn5BDKTW6auxdFYdYOgiDAYrF40k4phTAM8fnzZyilUBRF54rhOfIOF06SJMYPaPt8v99jOp3C8zx4nget9bPZQ5ZlF3fzL0IAZVke9OLv7+/Njl/brCHLMozHY4xGI3z48MH0EEVRIMuyi40H+EdqBbNS6HXBSqGEAiAUAAVAE1AAhAIgFAChAAgFQCgAQgGQq+Eom0GLxeJgGHYVSdCUhM02yrI0qV5hGGIymaAsy9b0LNd1cXt7CwDYbDa98wOA/zKLVquVSQGr/nYTbe2iKDIh53JtZVmiLEvsdjtst9tn5z7EDmfXA3QFXdaTMbvYbrdm568tgkdueJ7njbt3QwJA+8YJ1tsFQQDXdXFzc2N2E0Uwk8kEX758eXbMEDtY2QOsVqtn//v69SsAYL1eH9xK7dNGgjuiKMJ4PH4WmSN7+QBMFu/3798bn1oAzz47NvVrqmYgz2azRpv1scNV+wDVaN969y6JIEmSWBmyJenlIgZbcgvOzgmUqJxqkmY18ldCvGwkz/MntQcogBcgETrVMV98Aptvfh1JTKEAXsBms4HWGp7nYT6fw3Ec5Hlufbi253lQSkFr3VqmhgLoQVmW2G63ZigQx8/2my/FKCR17WLWAV7LfD5vzOFLkqS1W0/T1HT9RVFY5/jNZjMz3ouvorVGHMet9QheYoer7AGq478Y2LaiDTc3N3Bd90megSwG2YQVPcDQ+a/ccK01ttutWSWsetl/i7bfq16TzP1lGFgul0exw9X2AJLGJV3joRXCl3rnXbUDhmQKl2WJ9XoNrbV1vdXZCUCWWqvVQGR8HFIgqmuaKUiCSJcA+nrzWmvzdA/ZN6EAKlTz/eXmA3iSuXOoNEzfBRsA+PTpU+PnUjxSfnvo9/ZNR6cAakjFj2rqd3VtQJ6u1z5h1e+SdYbqdK5aWHLImC0OoFQgpRN4YPoD/LfRVC8C2TQlkhVC3/dfVDG0/l1xHCOKIvi+b572atJoURSdtYnbfAHxV0aj0TP/oY8dzqYH6OscHXK26tO+rqcujmNTIKqtJkDfc0vTFMvl8smu436/R57niOO4NSbh0HfLkFHtpYbY4dgwOfRKYXIooQAIBUAB0AQUAKEACAVAKABCARAKgFAA5Gp4s93AKIrw/v17ExsnFEWB/X6P3W6HLMtaN0+GJkwOad+W2FlPLq3GHFSRdq85h2PYyGoByG6cvJOnHiEryZJSg7e+s1ZNmOyzSza0ffWYJsIwbMzk7Tp+6Dm81kZWC0BoCnSU7dowDE2K12q1alT60EDJYwVWKqUQRdHgPf9jnfMQG52dDyA5fLKnLlGztiB5Bn1eP3fuNvr31IaWZM9jhHIdEwk5G1Jk4hxtdPJZQJZlJrLWlnBpx3FMmrnrup3RReduIyumgXJxtryRUxw4mQXIO4Yv0UZWCMDWN3I2vX7u0mxk1RtDmp6yoQmTbe27kjK7iOMYt7e3CIIA2+22VyLIWyZ5Hrsnsmol0Jac+fo51QtSXJKNrOgBuvLsTrUOUO8FxAP3ff/gTXiLc3irt5aevAdQSpmpja0vZqq+fm4ymfz18i5vaaOTC0DSvapv8rQRmRY6joPxeHwxNjqpAGSpUwx8ikKJQ5AyNFKb4BJsdBIfwPM8BEFgFjXSNG3debMJSUv7GyuWf8tGby6Aaq2c+qvaJce/a3p2ioTJQ73A3d3di6aBbef8WhtZKQDJ6K1fTJ7neHx8PFjWTcbbvvPePm8QbVtc6ft/+UwKUdfbDT3n19roGDA59EphciihAAgFQAHQBBQAoQAIBUAoAEIBEAqAUACEAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQC4CkxgiceKEPQC5Iv4/APgB2O7x8IXXAAAAAElFTkSuQmCC');
124+}
scripts/system/particle_explorer/particleExplorer.html+28−81
@@ -1,95 +1,42 @@
11 <!--
22 // particleExplorer.hml
3−//
3+//
44 //
55 // Created by James B. Pollack @imgntn on 9/26/2015
66 // Copyright 2015 High Fidelity, Inc.
77 //
8−// Loads dat.gui, underscore, and the app
9−// Quickly edit the aesthetics of a particle system.
8+
9+// Reworked by Menithal on 20/5/2017
10+// Using a custom built system for High Fidelity
1011 //
1112 // Distributed under the Apache License, Version 2.0.
1213 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
13−//
14− -->
14+//
15+ -->
1516 <html>
1617 <head>
17−<link rel="stylesheet" type="text/css" href="../html/css/colpick.css">
18−<script type="text/javascript" src="dat.gui.min.js"></script>
19−<script type="text/javascript" src="underscore-min.js"></script>
20−<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
21−<script type="text/javascript" src="../html/js/eventBridgeLoader.js"></script>
22−<script type="text/javascript" src="../html/js/jquery-2.1.4.min.js"></script>
23−<script type="text/javascript" src="../html/js/colpick.js"></script>
24−<script type="text/javascript" src="particleExplorer.js"></script>
25−<script>
26− function loaded() {
27− // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
28− document.addEventListener("contextmenu", function (event) {
29− event.preventDefault();
30− }, false);
31− }
32−</script>
33−<style>
34−
35−body{
36− background-color:black;
37− overflow-x: hidden;
38−}
39−
40−#my-gui-container{
41−
42−}
43−
44−.importer{
45− margin-bottom:4px;
46−}
47−.exported-props-section {
48− width: 50%;
49− margin: 0 auto;
50−
51−}
52−#exported-props {
53− /* Set the margin-left and margin-right automatically set */
54− color: white;
55− white-space: pre-wrap; /* css-3 */
56−}
57−
58−.color-box {
59− display: block;
60− width: 60%;
61− height: 21px;
62− margin-top: 4px;
63− float: left;
64−}
65−
66−.color-box.highlight {
67− width: 13.5pt;
68− height: 13.5pt;
69− border: 1.5pt solid black;
70−}
71−
72−::-webkit-input-placeholder {
73− text-align: center;
74− font-family: Helvetica
75−}
76−
77−#importer-input{
78− width:90%;
79− line-height: 2;
80− margin-left:5%;
81−}
82−
83−</style>
18+ <link rel="stylesheet" type="text/css" href="../html/css/colpick.css">
19+ <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
20+ <script type="text/javascript" src="../html/js/eventBridgeLoader.js"></script>
21+ <!---->
22+ <script type="text/javascript" src="../html/js/jquery-2.1.4.min.js"></script>
23+ <script type="text/javascript" src="../html/js/colpick.js"></script>
24+
25+ <script type="text/javascript" src="underscore-min.js"></script>
26+ <script type="text/javascript" src="hifi-entity-ui.js?v1"></script>
27+
28+ <link rel="stylesheet" type="text/css" href="../html/css/hifi-style.css">
29+ <link rel="stylesheet" type="text/css" href="../html/css/edit-style.css">
30+ <link rel="stylesheet" type="text/css" href="particle-style.css">
8431 </head>
85−<body onload="loaded();">
86−<div class="importer">
87−<input type='text' id="importer-input" placeholder="Import: Paste JSON here." onkeypress="handleInputKeyPress(event)">
88−<div class = "exported-props-section">
89− <div id = "exported-props"></div>
90−</div>
91−<div id="my-gui-container">
92−</div>
93−
32+<body>
33+ <div id="particle-explorer">
34+ <div class="section-header">
35+ <label> Particle Explorer </label>
36+ </div>
37+ <!-- This will be filled by the script! -->
38+ </div>
39+ <div id="rem"></div>
40+ <script type="text/javascript" src="particleExplorer.js"></script>
9441 </body>
9542 </html>
scripts/system/particle_explorer/particleExplorer.js+397−537
@@ -2,550 +2,410 @@ · 1 unmodified line
22 // particleExplorer.js
33 //
44 // Created by James B. Pollack @imgntn on 9/26/2015
5−// Copyright 2015 High Fidelity, Inc.
5+// Copyright 2017 High Fidelity, Inc.
6+//
7+// Reworked by Menithal on 20/5/2017
8+//
69 // Web app side of the App - contains GUI.
7−// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities.
10+// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities.
811 //
912 // Distributed under the Apache License, Version 2.0.
1013 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
1114 //
12−/* global window, alert, EventBridge, dat, listenForSettingsUpdates, createVec3Folder, createQuatFolder, writeVec3ToInterface, writeDataToInterface,
13− $, document, _, openEventBridge */
14−
15−var Settings = function () {
16− this.exportSettings = function () {
17− // copyExportSettingsToClipboard();
18− showPreselectedPrompt();
19− };
20− this.importSettings = function () {
21− importSettings();
22− };
23−};
24−
25−// 2-way bindings-aren't quite ready yet. see bottom of file.
26−var AUTO_UPDATE = false;
27−var UPDATE_ALL_FREQUENCY = 100;
28−
29−var controllers = [];
30−var colpickKeys = [];
31−var folders = [];
32−var gui = null;
33−var settings = new Settings();
34−var updateInterval;
35−
36−var active = false;
37−
38−var currentInputField;
39−var storedController;
40−// CHANGE TO WHITELIST
41−var keysToAllow = [
42− 'isEmitting',
43− 'maxParticles',
44− 'lifespan',
45− 'emitRate',
46− 'emitSpeed',
47− 'speedSpread',
48− 'emitOrientation',
49− 'emitDimensions',
50− 'polarStart',
51− 'polarFinish',
52− 'azimuthStart',
53− 'azimuthFinish',
54− 'emitAcceleration',
55− 'accelerationSpread',
56− 'particleRadius',
57− 'radiusSpread',
58− 'radiusStart',
59− 'radiusFinish',
60− 'color',
61− 'colorSpread',
62− 'colorStart',
63− 'colorFinish',
64− 'alpha',
65− 'alphaSpread',
66− 'alphaStart',
67− 'alphaFinish',
68− 'emitterShouldTrail',
69− 'textures'
70−];
71−
72−var individualKeys = [];
73−var vec3Keys = [];
74−var quatKeys = [];
75−var colorKeys = [];
76−
77−window.onload = function () {
78− openEventBridge(function () {
79− var stringifiedData = JSON.stringify({
80− messageType: 'page_loaded'
15+/* global HifiEntityUI, openEventBridge, console, EventBridge, document, window */
16+/* eslint no-console: 0, no-global-assign: 0 */
17+
18+(function () {
19+
20+ var root = document.getElementById("particle-explorer");
21+
22+ window.onload = function () {
23+ var ui = new HifiEntityUI(root);
24+ var textarea = document.createElement("textarea");
25+ var properties = "";
26+ var menuStructure = {
27+ General: [
28+ {
29+ type: "Row",
30+ id: "export-import-field"
31+ },
32+ {
33+ id: "show-properties-button",
34+ name: "Show Properties",
35+ type: "Button",
36+ class: "blue",
37+ disabled: true,
38+ callback: function (event) {
39+ var insertZone = document.getElementById("export-import-field");
40+ var json = ui.getSettings();
41+ properties = JSON.stringify(json);
42+ textarea.value = properties;
43+ if (!insertZone.contains(textarea)) {
44+ insertZone.appendChild(textarea);
45+ insertZone.parentNode.parentNode.style.maxHeight =
46+ insertZone.parentNode.clientHeight + "px";
47+ document.getElementById("export-properties-button").removeAttribute("disabled");
48+ textarea.onchange = function (e) {
49+ if (e.target.value !== properties) {
50+ document.getElementById("import-properties-button").removeAttribute("disabled");
51+ }
52+ };
53+ textarea.oninput = textarea.onchange;
54+ } else {
55+ textarea.onchange = function () {};
56+ textarea.oninput = textarea.onchange;
57+ textarea.value = "";
58+ textarea.remove();
59+ insertZone.parentNode.parentNode.style.maxHeight =
60+ insertZone.parentNode.clientHeight + "px";
61+ document.getElementById("export-properties-button").setAttribute("disabled", true);
62+ document.getElementById("import-properties-button").setAttribute("disabled", true);
63+ }
64+ }
65+ },
66+ {
67+ id: "import-properties-button",
68+ name: "Import",
69+ type: "Button",
70+ class: "blue",
71+ disabled: true,
72+ callback: function (event) {
73+ ui.fillFields(JSON.parse(textarea.value));
74+ ui.submitChanges(JSON.parse(textarea.value));
75+ }
76+ },
77+ {
78+ id: "export-properties-button",
79+ name: "Export",
80+ type: "Button",
81+ class: "red",
82+ disabled: true,
83+ callback: function (event) {
84+ textarea.select();
85+ try {
86+ var success = document.execCommand('copy');
87+ if (!success) {
88+ throw "Not success :(";
89+ }
90+ } catch (e) {
91+ print("couldnt copy field");
92+ }
93+ }
94+ },
95+ {
96+ type: "Row"
97+ },
98+ {
99+ id: "isEmitting",
100+ name: "Is Emitting",
101+ type: "Boolean"
102+ },
103+ {
104+ type: "Row"
105+ },
106+ {
107+ id: "lifespan",
108+ name: "Lifespan",
109+ type: "SliderFloat",
110+ min: 0.01,
111+ max: 10
112+ },
113+ {
114+ type: "Row"
115+ },
116+ {
117+ id: "maxParticles",
118+ name: "Max Particles",
119+ type: "SliderInteger",
120+ min: 1,
121+ max: 10000
122+ },
123+ {
124+ type: "Row"
125+ },
126+ {
127+ id: "textures",
128+ name: "Textures",
129+ type: "Texture"
130+ },
131+ {
132+ type: "Row"
133+ }
134+ ],
135+ Emit: [
136+ {
137+ id: "emitRate",
138+ name: "Emit Rate",
139+ type: "SliderInteger",
140+ max: 1000,
141+ min: 1
142+ },
143+ {
144+ type: "Row"
145+ },
146+ {
147+ id: "emitSpeed",
148+ name: "Emit Speed",
149+ type: "SliderFloat",
150+ max: 5
151+ },
152+ {
153+ type: "Row"
154+ },
155+ {
156+ id: "emitDimensions",
157+ name: "Emit Dimension",
158+ type: "Vector",
159+ defaultRange: {
160+ min: 0,
161+ step: 0.01
162+ }
163+ },
164+ {
165+ type: "Row"
166+ },
167+ {
168+ id: "emitOrientation",
169+ unit: "deg",
170+ name: "Emit Orientation",
171+ type: "VectorQuaternion",
172+ defaultRange: {
173+ min: 0,
174+ step: 0.01
175+ }
176+ },
177+ {
178+ type: "Row"
179+ },
180+ {
181+ id: "emitShouldTrail",
182+ name: "Emit Should Trail",
183+ type: "Boolean"
184+ },
185+ {
186+ type: "Row"
187+ }
188+ ],
189+ Radius: [
190+ {
191+ id: "particleRadius",
192+ name: "Particle Radius",
193+ type: "SliderFloat",
194+ max: 4.0
195+ },
196+ {
197+ type: "Row"
198+ },
199+ {
200+ id: "radiusSpread",
201+ name: "Radius Spread",
202+ type: "SliderFloat",
203+ max: 4.0
204+ },
205+ {
206+ type: "Row"
207+ },
208+ {
209+ id: "radiusStart",
210+ name: "Radius Start",
211+ type: "SliderFloat",
212+ max: 4.0
213+ },
214+ {
215+ type: "Row"
216+ },
217+ {
218+ id: "radiusFinish",
219+ name: "Radius Finish",
220+ type: "SliderFloat",
221+ max: 4.0
222+ },
223+ {
224+ type: "Row"
225+ }
226+ ],
227+ Color: [
228+ {
229+ id: "color",
230+ name: "Color",
231+ type: "Color",
232+ defaultColor: {
233+ red: 255,
234+ green: 255,
235+ blue: 255
236+ }
237+ },
238+ {
239+ type: "Row"
240+ },
241+ {
242+ id: "colorSpread",
243+ name: "Color Spread",
244+ type: "Color",
245+ defaultColor: {
246+ red: 0,
247+ green: 0,
248+ blue: 0
249+ }
250+ },
251+ {
252+ type: "Row"
253+ },
254+ {
255+ id: "colorStart",
256+ name: "Color Start",
257+ type: "Color",
258+ defaultColor: {
259+ red: 255,
260+ green: 255,
261+ blue: 255
262+ }
263+ },
264+ {
265+ type: "Row"
266+ },
267+ {
268+ id: "colorFinish",
269+ name: "Color Finish",
270+ type: "Color",
271+ defaultColor: {
272+ red: 255,
273+ green: 255,
274+ blue: 255
275+ }
276+ },
277+ {
278+ type: "Row"
279+ }
280+ ],
281+ Acceleration: [
282+ {
283+ id: "emitAcceleration",
284+ name: "Emit Acceleration",
285+ type: "Vector",
286+ defaultRange: {
287+ step: 0.01
288+ }
289+ },
290+ {
291+ type: "Row"
292+ },
293+ {
294+ id: "accelerationSpread",
295+ name: "Acceleration Spread",
296+ type: "Vector",
297+ defaultRange: {
298+ step: 0.01
299+ }
300+ },
301+ {
302+ type: "Row"
303+ }
304+ ],
305+ Alpha: [
306+ {
307+ id: "alpha",
308+ name: "Alpha",
309+ type: "SliderFloat"
310+ },
311+ {
312+ type: "Row"
313+ },
314+ {
315+ id: "alphaSpread",
316+ name: "Alpha Spread",
317+ type: "SliderFloat"
318+ },
319+ {
320+ type: "Row"
321+ },
322+ {
323+ id: "alphaStart",
324+ name: "Alpha Start",
325+ type: "SliderFloat"
326+ },
327+ {
328+ type: "Row"
329+ },
330+ {
331+ id: "alphaFinish",
332+ name: "Alpha Finish",
333+ type: "SliderFloat"
334+ },
335+ {
336+ type: "Row"
337+ }
338+ ],
339+ Polar: [
340+ {
341+ id: "polarStart",
342+ name: "Polar Start",
343+ unit: "deg",
344+ type: "SliderRadian"
345+ },
346+ {
347+ type: "Row"
348+ },
349+ {
350+ id: "polarFinish",
351+ name: "Polar Finish",
352+ unit: "deg",
353+ type: "SliderRadian"
354+ },
355+ {
356+ type: "Row"
357+ }
358+ ],
359+ Azimuth: [
360+ {
361+ id: "azimuthStart",
362+ name: "Azimuth Start",
363+ unit: "deg",
364+ type: "SliderRadian",
365+ min: -180,
366+ max: 0
367+ },
368+ {
369+ type: "Row"
370+ },
371+ {
372+ id: "azimuthFinish",
373+ name: "Azimuth Finish",
374+ unit: "deg",
375+ type: "SliderRadian"
376+ },
377+ {
378+ type: "Row"
379+ }
380+ ]
381+ };
382+ ui.setUI(menuStructure);
383+ ui.setOnSelect(function () {
384+ document.getElementById("show-properties-button").removeAttribute("disabled");
385+ document.getElementById("export-properties-button").setAttribute("disabled", true);
386+ document.getElementById("import-properties-button").setAttribute("disabled", true);
81387 });
82−
83− EventBridge.emitWebEvent(
84− stringifiedData
85− );
86−
87− listenForSettingsUpdates();
88− window.onresize = setGUIWidthToWindowWidth;
89− });
90−
91−};
92−
93−function loadGUI() {
94− // whether or not to autoplace
95− gui = new dat.GUI({
96− autoPlace: false
97− });
98−
99− // if not autoplacing, put gui in a custom container
100− if (gui.autoPlace === false) {
101− var customContainer = document.getElementById('my-gui-container');
102− customContainer.appendChild(gui.domElement);
103− }
104−
105− // presets for the GUI itself. a little confusing and import/export is mostly what we want to do at the moment.
106− // gui.remember(settings);
107−
108− colpickKeys = [];
109− var keys = _.keys(settings);
110−
111− _.each(keys, function(key) {
112− var shouldAllow = _.contains(keysToAllow, key);
113−
114− if (shouldAllow) {
115− var subKeys = _.keys(settings[key]);
116− var hasX = _.contains(subKeys, 'x');
117− var hasY = _.contains(subKeys, 'y');
118− var hasZ = _.contains(subKeys, 'z');
119− var hasW = _.contains(subKeys, 'w');
120− var hasRed = _.contains(subKeys, 'red');
121− var hasGreen = _.contains(subKeys, 'green');
122− var hasBlue = _.contains(subKeys, 'blue');
123−
124− if ((hasX && hasY && hasZ) && hasW === false) {
125− vec3Keys.push(key);
126− } else if (hasX && hasY && hasZ && hasW) {
127− quatKeys.push(key);
128− } else if (hasRed || hasGreen || hasBlue) {
129− colorKeys.push(key);
130−
131− } else {
132− individualKeys.push(key);
133− }
388+ ui.build();
389+ var overrideLoad = false;
390+ if (openEventBridge === undefined) {
391+ overrideLoad = true,
392+ openEventBridge = function (callback) {
393+ callback({
394+ emitWebEvent: function () {},
395+ submitChanges: function () {},
396+ scriptEventReceived: {
397+ connect: function () {
398+
399+ }
400+ }
401+ });
402+ };
134403 }
135− });
136−
137− // alphabetize our keys
138− individualKeys.sort();
139− vec3Keys.sort();
140− quatKeys.sort();
141− colorKeys.sort();
142−
143− // add to gui in the order they should appear
144− gui.add(settings, 'importSettings');
145− gui.add(settings, 'exportSettings');
146− addIndividualKeys();
147− addFolders();
148−
149− // set the gui width to match the web window width
150− gui.width = window.innerWidth;
151−
152− // 2-way binding stuff
153− // if (AUTO_UPDATE) {
154− // setInterval(manuallyUpdateDisplay, UPDATE_ALL_FREQUENCY);
155− // registerDOMElementsForListenerBlocking();
156− // }
157−
158−}
159−
160−function addIndividualKeys() {
161− _.each(individualKeys, function(key) {
162− // temporary patch for not crashing when this goes below 0
163− var controller;
164−
165− if (key.indexOf('emitRate') > -1) {
166− controller = gui.add(settings, key).min(0);
167− } else {
168− controller = gui.add(settings, key);
169− }
170−
171− // 2-way - need to fix not being able to input exact values if constantly listening
172− // controller.listen();
173−
174− // keep track of our controller
175− controllers.push(controller);
176−
177− // hook into change events for this gui controller
178− controller.onChange(function(value) {
179− // Fires on every change, drag, keypress, etc.
180− writeDataToInterface(this.property, value);
404+ openEventBridge(function (EventBridge) {
405+ ui.connect(EventBridge);
181406 });
182−
183− });
184−}
185−
186−function addFolders() {
187− _.each(colorKeys, function(key) {
188− createColorPicker(key);
189− });
190− _.each(vec3Keys, function(key) {
191− createVec3Folder(key);
192− });
193− _.each(quatKeys, function(key) {
194− createQuatFolder(key);
195− });
196−}
197−
198−function createColorPicker(key) {
199− var colorObject = settings[key];
200−
201− // Embed colpick's color picker into dat.GUI
202− var name = document.createElement('span');
203− name.className = 'property-name';
204− name.innerHTML = key;
205−
206− var container = document.createElement('div');
207− container.appendChild(name);
208−
209− var $colPickContainer = $('<div>', {
210− id: key.toString(),
211− class: "color-box"
212− });
213− $colPickContainer.css('background-color', "rgb(" + colorObject.red + "," + colorObject.green + "," + colorObject.blue + ")");
214− container.appendChild($colPickContainer[0]);
215−
216− var $li = $('<li>', { class: 'cr object color' });
217− $li.append(container);
218− gui.__ul.appendChild($li[0]);
219− gui.onResize();
220−
221− $colPickContainer.colpick({
222− colorScheme: 'dark',
223− layout: 'hex',
224− color: { r: colorObject.red, g: colorObject.green, b: colorObject.blue },
225− onSubmit: function (hsb, hex, rgb, el) {
226− $(el).css('background-color', '#' + hex);
227− $(el).colpickHide();
228−
229− var obj = {};
230− obj[key] = { red: rgb.r, green: rgb.g, blue: rgb.b };
231− writeVec3ToInterface(obj);
407+ if (overrideLoad) {
408+ openEventBridge();
232409 }
233− });
234−
235− colpickKeys.push(key);
236−}
237−
238−function createVec3Folder(category) {
239− var folder = gui.addFolder(category);
240−
241− folder.add(settings[category], 'x').step(0.1).onChange(function(value) {
242− // Fires when a controller loses focus.
243− var obj = {};
244− obj[category] = {};
245− obj[category][this.property] = value;
246− obj[category].y = settings[category].y;
247− obj[category].z = settings[category].z;
248− writeVec3ToInterface(obj);
249− });
250−
251− folder.add(settings[category], 'y').step(0.1).onChange(function(value) {
252− // Fires when a controller loses focus.
253− var obj = {};
254− obj[category] = {};
255− obj[category].x = settings[category].x;
256− obj[category][this.property] = value;
257− obj[category].z = settings[category].z;
258− writeVec3ToInterface(obj);
259− });
260−
261− folder.add(settings[category], 'z').step(0.1).onChange(function(value) {
262− // Fires when a controller loses focus.
263− var obj = {};
264− obj[category] = {};
265− obj[category].y = settings[category].y;
266− obj[category].x = settings[category].x;
267− obj[category][this.property] = value;
268− writeVec3ToInterface(obj);
269− });
270−
271− folders.push(folder);
272− folder.open();
273−}
274−
275−function createQuatFolder(category) {
276− var folder = gui.addFolder(category);
277−
278− folder.add(settings[category], 'x').step(0.1).onChange(function(value) {
279− // Fires when a controller loses focus.
280− var obj = {};
281− obj[category] = {};
282− obj[category][this.property] = value;
283− obj[category].y = settings[category].y;
284− obj[category].z = settings[category].z;
285− obj[category].w = settings[category].w;
286− writeVec3ToInterface(obj);
287− });
288−
289− folder.add(settings[category], 'y').step(0.1).onChange(function(value) {
290− // Fires when a controller loses focus.
291− var obj = {};
292− obj[category] = {};
293− obj[category].x = settings[category].x;
294− obj[category][this.property] = value;
295− obj[category].z = settings[category].z;
296− obj[category].w = settings[category].w;
297− writeVec3ToInterface(obj);
298− });
299−
300− folder.add(settings[category], 'z').step(0.1).onChange(function(value) {
301− // Fires when a controller loses focus.
302− var obj = {};
303− obj[category] = {};
304− obj[category].x = settings[category].x;
305− obj[category].y = settings[category].y;
306− obj[category][this.property] = value;
307− obj[category].w = settings[category].w;
308− writeVec3ToInterface(obj);
309− });
310−
311− folder.add(settings[category], 'w').step(0.1).onChange(function(value) {
312− // Fires when a controller loses focus.
313− var obj = {};
314− obj[category] = {};
315− obj[category].x = settings[category].x;
316− obj[category].y = settings[category].y;
317− obj[category].z = settings[category].z;
318− obj[category][this.property] = value;
319− writeVec3ToInterface(obj);
320− });
321−
322− folders.push(folder);
323− folder.open();
324−}
325−
326−function convertColorObjectToArray(colorObject) {
327− var colorArray = [];
328−
329− _.each(colorObject, function(singleColor) {
330− colorArray.push(singleColor);
331− });
332−
333− return colorArray;
334−}
335−
336−function convertColorArrayToObject(colorArray) {
337− var colorObject = {
338− red: colorArray[0],
339− green: colorArray[1],
340− blue: colorArray[2]
341− };
342−
343− return colorObject;
344−}
345−
346−function writeDataToInterface(property, value) {
347− var data = {};
348− data[property] = value;
349−
350− var sendData = {
351− messageType: "settings_update",
352− updatedSettings: data
353− };
354−
355− var stringifiedData = JSON.stringify(sendData);
356−
357− EventBridge.emitWebEvent(stringifiedData);
358−}
359−
360−function writeVec3ToInterface(obj) {
361− var sendData = {
362− messageType: "settings_update",
363− updatedSettings: obj
364− };
365−
366− var stringifiedData = JSON.stringify(sendData);
367−
368− EventBridge.emitWebEvent(stringifiedData);
369−}
370−
371−function listenForSettingsUpdates() {
372− EventBridge.scriptEventReceived.connect(function(data) {
373− data = JSON.parse(data);
374− if (data.messageType === 'particle_settings') {
375− _.each(data.currentProperties, function(value, key) {
376− settings[key] = {};
377− settings[key] = value;
378− });
379−
380− if (gui) {
381− manuallyUpdateDisplay();
382− } else {
383− loadGUI();
384− }
385− if (!active) {
386− // gui.toggleHide();
387− gui.closed = false;
388− }
389− active = true;
390−
391− } else if (data.messageType === "particle_close") {
392− // none of this seems to work.
393− // if (active) {
394− // gui.toggleHide();
395− // }
396− active = false;
397− gui.closed = true;
398− }
399− });
400−}
401−
402−function manuallyUpdateDisplay() {
403− // Iterate over all controllers
404− // this is expensive, write a method for indiviudal controllers and use it when the value is different than a cached value, perhaps.
405− var i;
406− for (i in gui.__controllers) {
407− gui.__controllers[i].updateDisplay();
408− }
409−
410− // Update color pickers
411− for (i in colpickKeys) {
412− var color = settings[colpickKeys[i]];
413− var $object = $('#' + colpickKeys[i]);
414− $object.css('background-color', "rgb(" + color.red + "," + color.green + "," + color.blue + ")");
415− $object.colpickSetColor({ r: color.red, g: color.green, b: color.blue }, true);
416− }
417−}
418−
419−function setGUIWidthToWindowWidth() {
420− if (gui !== null) {
421− gui.width = window.innerWidth;
422− }
423−}
424−
425−function handleInputKeyPress(e) {
426− if (e.keyCode === 13) {
427− importSettings();
428− }
429− return false;
430−}
431−
432−function importSettings() {
433− var importInput = document.getElementById('importer-input');
434−
435− try {
436− var importedSettings = JSON.parse(importInput.value);
437−
438− var keys = _.keys(importedSettings);
439−
440− _.each(keys, function(key) {
441− var shouldAllow = _.contains(keysToAllow, key);
442−
443− if (!shouldAllow) {
444− return;
445− }
446−
447− settings[key] = importedSettings[key];
448− });
449−
450− writeVec3ToInterface(settings);
451−
452− manuallyUpdateDisplay();
453− } catch (e) {
454− alert('Not properly formatted JSON');
455− }
456−}
457−
458−function prepareSettingsForExport() {
459− var keys = _.keys(settings);
460−
461− var exportSettings = {};
462−
463− _.each(keys, function(key) {
464− var shouldAllow = _.contains(keysToAllow, key);
465−
466− if (!shouldAllow) {
467− return;
468− }
469−
470− if (key.indexOf('color') > -1) {
471− var colorObject = convertColorArrayToObject(settings[key]);
472− settings[key] = colorObject;
473− }
474−
475− exportSettings[key] = settings[key];
476− });
477−
478− return JSON.stringify(exportSettings, null, 4);
479−}
480−
481−function showPreselectedPrompt() {
482− var elem = document.getElementById("exported-props");
483− var exportSettings = prepareSettingsForExport();
484− elem.innerHTML = "";
485− var buttonnode = document.createElement('input');
486− buttonnode.setAttribute('type', 'button');
487− buttonnode.setAttribute('value', 'close');
488− elem.appendChild(document.createTextNode("COPY THE BELOW FIELD TO CLIPBOARD:"));
489− elem.appendChild(document.createElement("br"));
490− var textAreaNode = document.createElement("textarea");
491− textAreaNode.value = exportSettings;
492− elem.appendChild(textAreaNode);
493− elem.appendChild(document.createElement("br"));
494− elem.appendChild(buttonnode);
495−
496− buttonnode.onclick = function() {
497− console.log("click");
498− elem.innerHTML = "";
499410 };
500−
501− // window.alert("Ctrl-C to copy, then Enter.", prepareSettingsForExport());
502−}
503−
504−function removeContainerDomElement() {
505− var elem = document.getElementById("my-gui-container");
506− elem.parentNode.removeChild(elem);
507−}
508−
509−function removeListenerFromGUI(key) {
510− _.each(gui.__listening, function(controller, index) {
511− if (controller.property === key) {
512− storedController = controller;
513− gui.__listening.splice(index, 1);
514− }
515− });
516−}
517−
518−// the section below is to try to work at achieving two way bindings;
519−
520−function addListenersBackToGUI() {
521− gui.__listening.push(storedController);
522− storedController = null;
523−}
524−
525−function registerDOMElementsForListenerBlocking() {
526− _.each(gui.__controllers, function(controller) {
527− var input = controller.domElement.childNodes[0];
528− input.addEventListener('focus', function() {
529− console.log('INPUT ELEMENT GOT FOCUS!' + controller.property);
530− removeListenerFromGUI(controller.property);
531− });
532− });
533−
534− _.each(gui.__controllers, function(controller) {
535− var input = controller.domElement.childNodes[0];
536− input.addEventListener('blur', function() {
537− console.log('INPUT ELEMENT GOT BLUR!' + controller.property);
538− addListenersBackToGUI();
539− });
540− });
541−
542− // also listen to inputs inside of folders
543− _.each(gui.__folders, function(folder) {
544− _.each(folder.__controllers, function(controller) {
545− var input = controller.__input;
546− input.addEventListener('focus', function() {
547− console.log('FOLDER ELEMENT GOT FOCUS!' + controller.property);
548− });
549− });
550− });
551−}
411+})();
scripts/system/particle_explorer/particleExplorerTool.js+6−4
@@ -4,23 +4,22 @@ · 3 unmodified lines
44 // Created by Eric Levin on 2/15/16
55 // Copyright 2016 High Fidelity, Inc.
66 // Adds particleExplorer tool to the edit panel when a user selects a particle entity from the edit tool window
7−// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities.
7+// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities.
88 //
99 // Distributed under the Apache License, Version 2.0.
1010 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
1111 //
12−/*global window, alert, EventBridge, dat, listenForSettingsUpdates,createVec3Folder,createQuatFolder,writeVec3ToInterface,writeDataToInterface*/
12+/* global window, alert, ParticleExplorerTool, EventBridge, dat, listenForSettingsUpdates,createVec3Folder,createQuatFolder,writeVec3ToInterface,writeDataToInterface*/
1313
1414
1515 var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html');
1616
1717 ParticleExplorerTool = function() {
1818 var that = {};
19−
2019 that.createWebView = function() {
2120 that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
2221 that.webView.setVisible = function(value) {};
23− that.webView.webEventReceived.connect(that.webEventReceived);
22+ that.webView.webEventReceived.connect(that.webEventReceived);
2423 }
2524
2625 that.destroyWebView = function() {
@@ -38,6 +37,9 @@ ParticleExplorerTool = function() { · 11 unmodified lines
3837 that.webEventReceived = function(data) {
3938 var data = JSON.parse(data);
4039 if (data.messageType === "settings_update") {
40+ if (data.updatedSettings.emitOrientation) {
41+ data.updatedSettings.emitOrientation = Quat.fromVec3Degrees(data.updatedSettings.emitOrientation);
42+ }
4143 Entities.editEntity(that.activeParticleEntity, data.updatedSettings);
4244 }
4345 }
scripts/system/progress.js+2−2
@@ -261,7 +261,7 @@ · 260 unmodified lines
261261 }
262262 }
263263
264− gpuTextures = Render.getConfig("Stats").textureGPUTransferCount;
264+ gpuTextures = Render.getConfig("Stats").texturePendingGPUTransferCount;
265265
266266 // Update state
267267 if (!visible) { // Not visible because no recent downloads
@@ -290,7 +290,7 @@ · 22 unmodified lines
290290 }, FADE_OUT_WAIT);
291291 }
292292 } else {
293− if (displayProgress < 100 || gpuTextures > 0) { // Was finished and waiting to fade out but have resumed so
293+ if (displayProgress < 100 || gpuTextures > 0) { // Was finished and waiting to fade out but have resumed so
294294 // don't fade out
295295 Script.clearInterval(fadeWaitTimer);
296296 fadeWaitTimer = null;
scripts/system/snapshot.js+172−18
@@ -36,6 +36,8 @@ var shareAfterLogin = false; · 35 unmodified lines
3636 var snapshotToShareAfterLogin = [];
3737 var METAVERSE_BASE = location.metaverseServerUrl;
3838 var isLoggedIn;
39+var numGifSnapshotUploadsPending = 0;
40+var numStillSnapshotUploadsPending = 0;
3941
4042 // It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story,
4143 // POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS
@@ -56,6 +58,10 @@ function removeFromStoryIDsToMaybeDelete(story_id) { · 14 unmodified lines
5658 print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete));
5759 }
5860
61+function fileExtensionMatches(filePath, extension) {
62+ return filePath.split('.').pop().toLowerCase() === extension;
63+}
64+
5965 function onMessage(message) {
6066 // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following:
6167 // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.)
@@ -93,11 +99,13 @@ function onMessage(message) { · 31 unmodified lines
9399 Settings.setValue("previousStillSnapStoryID", "");
94100 Settings.setValue("previousStillSnapBlastingDisabled", false);
95101 Settings.setValue("previousStillSnapHifiSharingDisabled", false);
102+ Settings.setValue("previousStillSnapUrl", "");
96103 Settings.setValue("previousAnimatedSnapPath", "");
97104 Settings.setValue("previousAnimatedSnapStoryID", "");
98105 Settings.setValue("previousAnimatedSnapBlastingDisabled", false);
99106 Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false);
100107 }
108+ updatePrintPermissions();
101109 break;
102110 case 'login':
103111 openLoginWindow();
@@ -134,16 +142,24 @@ function onMessage(message) { · 30 unmodified lines
134142 break;
135143 case 'shareSnapshotForUrl':
136144 isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) {
137− if (canShare) {
138− isLoggedIn = Account.isLoggedIn();
145+ var isGif = fileExtensionMatches(message.data, "gif");
146+ isLoggedIn = Account.isLoggedIn();
147+ isUploadingPrintableStill = canShare && Account.isLoggedIn() && !isGif;
148+ if (canShare) {
139149 if (isLoggedIn) {
140150 print('Sharing snapshot with audience "for_url":', message.data);
141− Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref"));
151+ Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref"));
152+ if (isGif) {
153+ numGifSnapshotUploadsPending++;
154+ } else {
155+ numStillSnapshotUploadsPending++;
156+ }
142157 } else {
143158 shareAfterLogin = true;
144159 snapshotToShareAfterLogin.push({ path: message.data, href: Settings.getValue("previousSnapshotHref") });
145160 }
146161 }
162+ updatePrintPermissions();
147163 });
148164 break;
149165 case 'blastToConnections':
@@ -203,6 +219,18 @@ function onMessage(message) { · 53 unmodified lines
203219 });
204220 }
205221 break;
222+ case 'requestPrintButtonUpdate':
223+ updatePrintPermissions();
224+ break;
225+ case 'printToPolaroid':
226+ if (Entities.canRez() || Entities.canRezTmp()) {
227+ printToPolaroid(Settings.getValue("previousStillSnapUrl"));
228+ removeFromStoryIDsToMaybeDelete(Settings.getValue("previousStillSnapStoryID"));
229+ }
230+ break;
231+ case 'alertSnapshotLoadFailed':
232+ snapshotFailedToLoad = true;
233+ break;
206234 case 'shareSnapshotWithEveryone':
207235 isLoggedIn = Account.isLoggedIn();
208236 if (isLoggedIn) {
@@ -250,6 +278,54 @@ function onMessage(message) { · 41 unmodified lines
250278 }
251279 }
252280
281+var POLAROID_PRINT_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/sound-print-photo.wav"));
282+var POLAROID_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx';
283+
284+function printToPolaroid(image_url) {
285+ var polaroid_url = image_url;
286+
287+ var model_pos = Vec3.sum(MyAvatar.position, Vec3.multiply(1.25, Quat.getForward(MyAvatar.orientation)));
288+
289+ var model_q1 = MyAvatar.orientation;
290+ var model_q2 = Quat.angleAxis(90, Quat.getRight(model_q1));
291+ var model_rot = Quat.multiply(model_q2, model_q1);
292+
293+ var properties = {
294+ "type": 'Model',
295+ "shapeType": 'box',
296+
297+ "name": "New Snapshot",
298+ "description": "Printed from Snaps",
299+ "modelURL": POLAROID_MODEL_URL,
300+
301+ "position": model_pos,
302+ "rotation": model_rot,
303+
304+ "textures": JSON.stringify( { "tex.picture": polaroid_url } ),
305+
306+ "density": 200,
307+ "restitution": 0.15,
308+ "gravity": { "x": 0, "y": -4.5, "z": 0 },
309+
310+ "velocity": { "x": 0, "y": 3.5, "z": 0 },
311+ "angularVelocity": { "x": -1.0, "y": 0, "z": -1.3 },
312+
313+ "dynamic": true,
314+ "collisionsWillMove": true,
315+
316+ "userData": {
317+ "grabbableKey": { "grabbable" : true }
318+ }
319+ };
320+
321+ var polaroid = Entities.addEntity(properties);
322+ Audio.playSound(POLAROID_PRINT_SOUND, {
323+ position: model_pos,
324+ localOnly: false,
325+ volume: 0.2
326+ });
327+}
328+
253329 function fillImageDataFromPrevious() {
254330 isLoggedIn = Account.isLoggedIn();
255331 var previousStillSnapPath = Settings.getValue("previousStillSnapPath");
@@ -273,7 +349,8 @@ function fillImageDataFromPrevious() { · 17 unmodified lines
273349 localPath: previousStillSnapPath,
274350 story_id: previousStillSnapStoryID,
275351 blastButtonDisabled: previousStillSnapBlastingDisabled,
276− hifiButtonDisabled: previousStillSnapHifiSharingDisabled
352+ hifiButtonDisabled: previousStillSnapHifiSharingDisabled,
353+ errorPath: Script.resolvePath(Script.resourcesPath() + 'snapshot/img/no-image.jpg')
277354 });
278355 }
279356 if (previousAnimatedSnapPath !== "") {
@@ -281,7 +358,8 @@ function fillImageDataFromPrevious() { · 1 unmodified line
281358 localPath: previousAnimatedSnapPath,
282359 story_id: previousAnimatedSnapStoryID,
283360 blastButtonDisabled: previousAnimatedSnapBlastingDisabled,
284− hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled
361+ hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled,
362+ errorPath: Script.resolvePath(Script.resourcesPath() + 'snapshot/img/no-image.jpg')
285363 });
286364 }
287365 }
@@ -305,26 +383,45 @@ function onButtonClicked() { · 17 unmodified lines
305383
306384 function snapshotUploaded(isError, reply) {
307385 if (!isError) {
308− var replyJson = JSON.parse(reply);
309− var storyID = replyJson.user_story.id;
386+ var replyJson = JSON.parse(reply),
387+ storyID = replyJson.user_story.id,
388+ imageURL = replyJson.user_story.details.image_url,
389+ isGif = fileExtensionMatches(imageURL, "gif"),
390+ ignoreGifSnapshotData = false,
391+ ignoreStillSnapshotData = false;
310392 storyIDsToMaybeDelete.push(storyID);
311− var imageURL = replyJson.user_story.details.image_url;
312− var isGif = imageURL.split('.').pop().toLowerCase() === "gif";
313− print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID);
314− tablet.emitScriptEvent(JSON.stringify({
315− type: "snapshot",
316− action: "snapshotUploadComplete",
317− story_id: storyID,
318− image_url: imageURL,
319− }));
320393 if (isGif) {
321− Settings.setValue("previousAnimatedSnapStoryID", storyID);
394+ numGifSnapshotUploadsPending--;
395+ if (numGifSnapshotUploadsPending !== 0) {
396+ ignoreGifSnapshotData = true;
397+ }
398+ } else {
399+ numStillSnapshotUploadsPending--;
400+ if (numStillSnapshotUploadsPending !== 0) {
401+ ignoreStillSnapshotData = true;
402+ }
403+ }
404+ if ((isGif && !ignoreGifSnapshotData) || (!isGif && !ignoreStillSnapshotData)) {
405+ print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID);
406+ tablet.emitScriptEvent(JSON.stringify({
407+ type: "snapshot",
408+ action: "snapshotUploadComplete",
409+ story_id: storyID,
410+ image_url: imageURL,
411+ }));
412+ if (isGif) {
413+ Settings.setValue("previousAnimatedSnapStoryID", storyID);
414+ } else {
415+ Settings.setValue("previousStillSnapStoryID", storyID);
416+ Settings.setValue("previousStillSnapUrl", imageURL);
417+ }
322418 } else {
323− Settings.setValue("previousStillSnapStoryID", storyID);
419+ print('Ignoring snapshotUploaded() callback for stale ' + (isGif ? 'GIF' : 'Still' ) + ' snapshot. Stale story ID:', storyID);
324420 }
325421 } else {
326422 print(reply);
327423 }
424+ isUploadingPrintableStill = false;
328425 }
329426 var href, domainId;
330427 function takeSnapshot() {
@@ -336,10 +433,16 @@ function takeSnapshot() { · 5 unmodified lines
336433 Settings.setValue("previousStillSnapStoryID", "");
337434 Settings.setValue("previousStillSnapBlastingDisabled", false);
338435 Settings.setValue("previousStillSnapHifiSharingDisabled", false);
436+ Settings.setValue("previousStillSnapUrl", "");
339437 Settings.setValue("previousAnimatedSnapPath", "");
340438 Settings.setValue("previousAnimatedSnapStoryID", "");
341439 Settings.setValue("previousAnimatedSnapBlastingDisabled", false);
342440 Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false);
441+
442+ // Since we are taking a snapshot, we should make the print button appear to be loading/processing
443+ snapshotFailedToLoad = false;
444+ isUploadingPrintableStill = true;
445+ updatePrintPermissions();
343446
344447 // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving.
345448 // Turn it off now, before we start futzing with things (and possibly moving).
@@ -379,8 +482,15 @@ function takeSnapshot() { · 33 unmodified lines
379482 Menu.setIsOptionChecked("Overlays", false);
380483 }
381484
485+ var snapActivateSound = SoundCache.getSound(Script.resolvePath("../../resources/sounds/snap.wav"));
486+
382487 // take snapshot (with no notification)
383488 Script.setTimeout(function () {
489+ Audio.playSound(snapActivateSound, {
490+ position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z },
491+ localOnly: true,
492+ volume: 1.0
493+ });
384494 HMD.closeTablet();
385495 Script.setTimeout(function () {
386496 Window.takeSnapshot(false, includeAnimated, 1.91);
@@ -566,8 +676,16 @@ function onUsernameChanged() { · 179 unmodified lines
566676 snapshotToShareAfterLogin.forEach(function (element) {
567677 print('Uploading snapshot after login:', element.path);
568678 Window.shareSnapshot(element.path, element.href);
679+ var isGif = fileExtensionMatches(element.path, "gif");
680+ if (isGif) {
681+ numGifSnapshotUploadsPending++;
682+ } else {
683+ numStillSnapshotUploadsPending++;
684+ }
569685 });
570686 }
687+ isUploadingPrintableStill = canShare;
688+ updatePrintPermissions();
571689 });
572690
573691 shareAfterLogin = false;
@@ -575,6 +693,7 @@ function onUsernameChanged() { · 1 unmodified line
575693 }
576694 }
577695 }
696+
578697 function snapshotLocationSet(location) {
579698 if (location !== "") {
580699 tablet.emitScriptEvent(JSON.stringify({
@@ -584,12 +703,44 @@ function snapshotLocationSet(location) { · 3 unmodified lines
584703 }
585704 }
586705
706+function updatePrintPermissions() {
707+ processRezPermissionChange(Entities.canRez() || Entities.canRezTmp());
708+}
709+
710+var snapshotFailedToLoad = false;
711+var isUploadingPrintableStill = false;
712+function processRezPermissionChange(canRez) {
713+ var action = "";
714+
715+ if (canRez && !snapshotFailedToLoad) {
716+ if (Settings.getValue("previousStillSnapUrl")) {
717+ action = 'setPrintButtonEnabled';
718+ } else if (isUploadingPrintableStill) {
719+ action = 'setPrintButtonLoading';
720+ } else {
721+ action = 'setPrintButtonDisabled';
722+ }
723+ } else {
724+ action = 'setPrintButtonDisabled';
725+ }
726+
727+ tablet.emitScriptEvent(JSON.stringify({
728+ type: "snapshot",
729+ action : action
730+ }));
731+}
732+
587733 button.clicked.connect(onButtonClicked);
588734 buttonConnected = true;
735+
589736 Window.snapshotShared.connect(snapshotUploaded);
590737 tablet.screenChanged.connect(onTabletScreenChanged);
591738 GlobalServices.myUsernameChanged.connect(onUsernameChanged);
592739 Snapshot.snapshotLocationSet.connect(snapshotLocationSet);
740+
741+Entities.canRezChanged.connect(updatePrintPermissions);
742+Entities.canRezTmpChanged.connect(updatePrintPermissions);
743+
593744 Script.scriptEnding.connect(function () {
594745 if (buttonConnected) {
595746 button.clicked.disconnect(onButtonClicked);
@@ -601,6 +752,9 @@ Script.scriptEnding.connect(function () { · 5 unmodified lines
601752 Window.snapshotShared.disconnect(snapshotUploaded);
602753 tablet.screenChanged.disconnect(onTabletScreenChanged);
603754 Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet);
755+
756+ Entities.canRezChanged.disconnect(processRezPermissionChange);
757+ Entities.canRezTmpChanged.disconnect(processRezPermissionChange);
604758 });
605759
606760 }()); // END LOCAL_SCOPE
scripts/system/tablet-ui/tabletUI.js+2−6
@@ -188,16 +188,12 @@ · 187 unmodified lines
188188 gTablet.updateAudioBar(getMicLevel());
189189 }
190190
191− if (validCheckTime - now > MSECS_PER_SEC/4) {
192− //each 250ms should be just fine
191+ if (now - validCheckTime > MSECS_PER_SEC) {
192+ validCheckTime = now;
193193 updateTabletWidthFromSettings();
194194 if (UIWebTablet) {
195195 UIWebTablet.setLandscape(landscape);
196196 }
197− }
198−
199− if (validCheckTime - now > MSECS_PER_SEC) {
200− validCheckTime = now;
201197 if (tabletRezzed && UIWebTablet && !tabletIsValid()) {
202198 // when we switch domains, the tablet entity gets destroyed and recreated. this causes
203199 // the overlay to be deleted, but not recreated. If the overlay is deleted for this or any
server-console/src/main.js+23−25
@@ -821,6 +821,17 @@ for (var key in trayIcons) { · 820 unmodified lines
821821
822822 const notificationIcon = path.join(__dirname, '../resources/console-notification.png');
823823
824+function isProcessRunning(pid) {
825+ try {
826+ // Sending a signal of 0 is effectively a NOOP.
827+ // If sending the signal is successful, kill will return true.
828+ // If the process is not running, an exception will be thrown.
829+ return process.kill(pid, 0);
830+ } catch (e) {
831+ }
832+ return false;
833+}
834+
824835 function onContentLoaded() {
825836 // Disable splash window for now.
826837 // maybeShowSplash();
@@ -882,31 +893,18 @@ function onContentLoaded() { · 55 unmodified lines
882893 startInterface();
883894 }
884895
885− // If we were launched with the shutdownWatcher option, then we need to watch for the interface app
886− // shutting down. The interface app will regularly update a running state file which we will check.
887− // If the file doesn't exist or stops updating for a significant amount of time, we will shut down.
888− if (argv.shutdownWatcher) {
889− log.debug("Shutdown watcher requested... argv.shutdownWatcher:", argv.shutdownWatcher);
890− var MAX_TIME_SINCE_EDIT = 5000; // 5 seconds between updates
891− var firstAttemptToCheck = new Date().getTime();
892− var shutdownWatchInterval = setInterval(function(){
893− var stats = fs.stat(argv.shutdownWatcher, function(err, stats) {
894− if (err) {
895− var sinceFirstCheck = new Date().getTime() - firstAttemptToCheck;
896− if (sinceFirstCheck > MAX_TIME_SINCE_EDIT) {
897− log.debug("Running state file is missing, assume interface has shutdown... shutting down snadbox.");
898− forcedShutdown();
899− clearTimeout(shutdownWatchInterval);
900− }
901− } else {
902− var sinceEdit = new Date().getTime() - stats.mtime.getTime();
903− if (sinceEdit > MAX_TIME_SINCE_EDIT) {
904− log.debug("Running state of interface hasn't updated in MAX time... shutting down.");
905− forcedShutdown();
906− clearTimeout(shutdownWatchInterval);
907− }
908− }
909− });
896+ // If we were launched with the shutdownWith option, then we need to shutdown when that process (pid)
897+ // is no longer running.
898+ if (argv.shutdownWith) {
899+ let pid = argv.shutdownWith;
900+ console.log("Shutting down with process: ", pid);
901+ let checkProcessInterval = setInterval(function() {
902+ let isRunning = isProcessRunning(pid);
903+ if (!isRunning) {
904+ log.debug("Watched process is no longer running, shutting down");
905+ clearTimeout(checkProcessInterval);
906+ forcedShutdown();
907+ }
910908 }, 1000);
911909 }
912910 }
tests/controllers/src/main.cpp+8−0
@@ -122,6 +122,10 @@ int main(int argc, char** argv) { · 121 unmodified lines
122122 glm::mat4(),
123123 glm::mat4(),
124124 glm::mat4(),
125+ glm::mat4(),
126+ glm::mat4(),
127+ glm::mat4(),
128+ glm::mat4(),
125129 glm::mat4()
126130 };
127131
@@ -144,6 +148,10 @@ int main(int argc, char** argv) { · 16 unmodified lines
144148 glm::mat4(),
145149 glm::mat4(),
146150 glm::mat4(),
151+ glm::mat4(),
152+ glm::mat4(),
153+ glm::mat4(),
154+ glm::mat4(),
147155 glm::mat4()
148156 };
149157
tests/ktx/CMakeLists.txt+7−13
@@ -1,15 +1,9 @@
1+# Declare dependencies
2+macro (SETUP_TESTCASE_DEPENDENCIES)
3+ # link in the shared libraries
4+ link_hifi_libraries(shared ktx gpu image)
15
2−set(TARGET_NAME ktx-test)
3−
4−if (WIN32)
5− SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217")
6−endif()
6+ package_libraries_for_deployment()
7+endmacro ()
78
8−# This is not a testcase -- just set it up as a regular hifi project
9−setup_hifi_project(Quick Gui OpenGL)
10−set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
11−
12−# link in the shared libraries
13−link_hifi_libraries(shared octree ktx gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics image)
14−
15−package_libraries_for_deployment()
9+setup_hifi_testcase()
tests/ktx/src/KtxTests.cpp+195−0
@@ -0,0 +1,195 @@
1+//
2+// Created by Bradley Austin Davis on 2016/07/01
3+// Copyright 2014 High Fidelity, Inc.
4+//
5+// Distributed under the Apache License, Version 2.0.
6+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
7+//
8+
9+#include "KtxTests.h"
10+
11+#include <mutex>
12+
13+#include <QtTest/QtTest>
14+
15+#include <ktx/KTX.h>
16+#include <gpu/Texture.h>
17+#include <image/Image.h>
18+
19+
20+QTEST_GUILESS_MAIN(KtxTests)
21+
22+QString getRootPath() {
23+ static std::once_flag once;
24+ static QString result;
25+ std::call_once(once, [&] {
26+ QFileInfo file(__FILE__);
27+ QDir parent = file.absolutePath();
28+ result = QDir::cleanPath(parent.currentPath() + "/../../..");
29+ });
30+ return result;
31+}
32+
33+void KtxTests::initTestCase() {
34+}
35+
36+void KtxTests::cleanupTestCase() {
37+}
38+
39+void KtxTests::testKhronosCompressionFunctions() {
40+ using namespace khronos::gl::texture;
41+ QCOMPARE(evalAlignedCompressedBlockCount<4>(0), (uint32_t)0x0);
42+ QCOMPARE(evalAlignedCompressedBlockCount<4>(1), (uint32_t)0x1);
43+ QCOMPARE(evalAlignedCompressedBlockCount<4>(4), (uint32_t)0x1);
44+ QCOMPARE(evalAlignedCompressedBlockCount<4>(5), (uint32_t)0x2);
45+ QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x00), (uint32_t)0x00);
46+ QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x01), (uint32_t)0x01);
47+ QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x04), (uint32_t)0x01);
48+ QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x05), (uint32_t)0x02);
49+ QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x1000), (uint32_t)0x400);
50+
51+ QVERIFY_EXCEPTION_THROWN(evalCompressedBlockCount(InternalFormat::RGBA8, 0x00), std::runtime_error);
52+}
53+
54+void KtxTests::testKtxEvalFunctions() {
55+ QCOMPARE(sizeof(ktx::Header), (size_t)64);
56+ QCOMPARE(ktx::evalPadding(0x0), (uint8_t)0);
57+ QCOMPARE(ktx::evalPadding(0x1), (uint8_t)3);
58+ QCOMPARE(ktx::evalPadding(0x2), (uint8_t)2);
59+ QCOMPARE(ktx::evalPadding(0x3), (uint8_t)1);
60+ QCOMPARE(ktx::evalPadding(0x4), (uint8_t)0);
61+ QCOMPARE(ktx::evalPadding(0x400), (uint8_t)0);
62+ QCOMPARE(ktx::evalPadding(0x401), (uint8_t)3);
63+ QCOMPARE(ktx::evalPaddedSize(0x0), 0x0);
64+ QCOMPARE(ktx::evalPaddedSize(0x1), 0x4);
65+ QCOMPARE(ktx::evalPaddedSize(0x2), 0x4);
66+ QCOMPARE(ktx::evalPaddedSize(0x3), 0x4);
67+ QCOMPARE(ktx::evalPaddedSize(0x4), 0x4);
68+ QCOMPARE(ktx::evalPaddedSize(0x400), 0x400);
69+ QCOMPARE(ktx::evalPaddedSize(0x401), 0x404);
70+ QCOMPARE(ktx::evalAlignedCount((uint32_t)0x0), (uint32_t)0x0);
71+ QCOMPARE(ktx::evalAlignedCount((uint32_t)0x1), (uint32_t)0x1);
72+ QCOMPARE(ktx::evalAlignedCount((uint32_t)0x4), (uint32_t)0x1);
73+ QCOMPARE(ktx::evalAlignedCount((uint32_t)0x5), (uint32_t)0x2);
74+}
75+
76+void KtxTests::testKtxSerialization() {
77+ const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png";
78+ QImage image(TEST_IMAGE);
79+ gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true);
80+ auto ktxMemory = gpu::Texture::serialize(*testTexture);
81+ QVERIFY(ktxMemory.get());
82+
83+ // Serialize the image to a file
84+ QTemporaryFile TEST_IMAGE_KTX;
85+ {
86+ const auto& ktxStorage = ktxMemory->getStorage();
87+ QVERIFY(ktx::KTX::validate(ktxStorage));
88+ QVERIFY(ktxMemory->isValid());
89+
90+ auto& outFile = TEST_IMAGE_KTX;
91+ if (!outFile.open()) {
92+ QFAIL("Unable to open file");
93+ }
94+ auto ktxSize = ktxStorage->size();
95+ outFile.resize(ktxSize);
96+ auto dest = outFile.map(0, ktxSize);
97+ memcpy(dest, ktxStorage->data(), ktxSize);
98+ outFile.unmap(dest);
99+ outFile.close();
100+ }
101+
102+
103+ {
104+ auto ktxStorage = std::make_shared<storage::FileStorage>(TEST_IMAGE_KTX.fileName());
105+ QVERIFY(ktx::KTX::validate(ktxStorage));
106+ auto ktxFile = ktx::KTX::create(ktxStorage);
107+ QVERIFY(ktxFile.get());
108+ QVERIFY(ktxFile->isValid());
109+ {
110+ const auto& memStorage = ktxMemory->getStorage();
111+ const auto& fileStorage = ktxFile->getStorage();
112+ QVERIFY(memStorage->size() == fileStorage->size());
113+ QVERIFY(memStorage->data() != fileStorage->data());
114+ QVERIFY(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size()));
115+ QVERIFY(ktxFile->_images.size() == ktxMemory->_images.size());
116+ auto imageCount = ktxFile->_images.size();
117+ auto startMemory = ktxMemory->_storage->data();
118+ auto startFile = ktxFile->_storage->data();
119+ for (size_t i = 0; i < imageCount; ++i) {
120+ auto memImages = ktxMemory->_images[i];
121+ auto fileImages = ktxFile->_images[i];
122+ QVERIFY(memImages._padding == fileImages._padding);
123+ QVERIFY(memImages._numFaces == fileImages._numFaces);
124+ QVERIFY(memImages._imageSize == fileImages._imageSize);
125+ QVERIFY(memImages._faceSize == fileImages._faceSize);
126+ QVERIFY(memImages._faceBytes.size() == memImages._numFaces);
127+ QVERIFY(fileImages._faceBytes.size() == fileImages._numFaces);
128+ auto faceCount = fileImages._numFaces;
129+ for (uint32_t face = 0; face < faceCount; ++face) {
130+ auto memFace = memImages._faceBytes[face];
131+ auto memOffset = memFace - startMemory;
132+ auto fileFace = fileImages._faceBytes[face];
133+ auto fileOffset = fileFace - startFile;
134+ QVERIFY(memOffset % 4 == 0);
135+ QVERIFY(memOffset == fileOffset);
136+ }
137+ }
138+ }
139+ }
140+ testTexture->setKtxBacking(TEST_IMAGE_KTX.fileName().toStdString());
141+}
142+
143+#if 0
144+
145+static const QString TEST_FOLDER { "H:/ktx_cacheold" };
146+//static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" };
147+
148+//static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" };
149+static const QString EXTENSIONS { "*.ktx" };
150+
151+int mainTemp(int, char**) {
152+ qInstallMessageHandler(messageHandler);
153+ auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS });
154+ for (auto fileInfo : fileInfoList) {
155+ qDebug() << fileInfo.filePath();
156+ std::shared_ptr<storage::Storage> storage { new storage::FileStorage { fileInfo.filePath() } };
157+
158+ if (!ktx::KTX::validate(storage)) {
159+ qDebug() << "KTX invalid";
160+ }
161+
162+ auto ktxFile = ktx::KTX::create(storage);
163+ ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor();
164+
165+ qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs";
166+ for (const auto& kv : ktxDescriptor.keyValues) {
167+ qDebug() << "\t" << kv._key.c_str();
168+ }
169+
170+ auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
171+ if (offsetToMinMipKV) {
172+ auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV;
173+ auto minMipLevelAvailable = *data;
174+ qDebug() << "\tMin mip available " << minMipLevelAvailable;
175+ assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels);
176+ }
177+ auto storageSize = storage->size();
178+ for (const auto& faceImageDesc : ktxDescriptor.images) {
179+ //assert(0 == (faceImageDesc._faceSize % 4));
180+ for (const auto& faceOffset : faceImageDesc._faceOffsets) {
181+ assert(0 == (faceOffset % 4));
182+ auto faceEndOffset = faceOffset + faceImageDesc._faceSize;
183+ assert(faceEndOffset <= storageSize);
184+ }
185+ }
186+
187+ for (const auto& faceImage : ktxFile->_images) {
188+ for (const ktx::Byte* faceBytes : faceImage._faceBytes) {
189+ assert(0 == (reinterpret_cast<size_t>(faceBytes) % 4));
190+ }
191+ }
192+ }
193+ return 0;
194+}
195+#endif
tests/ktx/src/KtxTests.h+21−0
@@ -0,0 +1,21 @@
1+//
2+// Created by Bradley Austin Davis on 2016/07/01
3+// Copyright 2014 High Fidelity, Inc.
4+//
5+// Distributed under the Apache License, Version 2.0.
6+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
7+//
8+
9+#include <QtCore/QObject>
10+
11+class KtxTests : public QObject {
12+ Q_OBJECT
13+private slots:
14+ void initTestCase();
15+ void cleanupTestCase();
16+ void testKtxEvalFunctions();
17+ void testKhronosCompressionFunctions();
18+ void testKtxSerialization();
19+};
20+
21+
tests/ktx/src/main.cpp+0−223
@@ -1,223 +0,0 @@
1−//
2−// Created by Bradley Austin Davis on 2016/07/01
3−// Copyright 2014 High Fidelity, Inc.
4−//
5−// Distributed under the Apache License, Version 2.0.
6−// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
7−//
8−
9−#include <iostream>
10−#include <string>
11−#include <vector>
12−#include <sstream>
13−
14−#include <QProcessEnvironment>
15−
16−#include <QtCore/QDir>
17−#include <QtCore/QElapsedTimer>
18−#include <QtCore/QLoggingCategory>
19−#include <QtCore/QRegularExpression>
20−#include <QtCore/QSettings>
21−#include <QtCore/QTimer>
22−#include <QtCore/QThread>
23−#include <QtCore/QThreadPool>
24−#include <QtCore/QSaveFile>
25−
26−#include <QtGui/QGuiApplication>
27−#include <QtGui/QResizeEvent>
28−#include <QtGui/QWindow>
29−
30−#include <QtWidgets/QFileDialog>
31−#include <QtWidgets/QInputDialog>
32−#include <QtWidgets/QMessageBox>
33−#include <QtWidgets/QApplication>
34−
35−#include <shared/RateCounter.h>
36−#include <shared/NetworkUtils.h>
37−#include <shared/FileLogger.h>
38−#include <shared/FileUtils.h>
39−#include <StatTracker.h>
40−#include <LogHandler.h>
41−
42−#include <gpu/Texture.h>
43−#include <gl/Config.h>
44−#include <model/TextureMap.h>
45−#include <ktx/KTX.h>
46−#include <image/Image.h>
47−
48−QSharedPointer<FileLogger> logger;
49−
50−gpu::Texture* cacheTexture(const std::string& name, gpu::Texture* srcTexture, bool write = true, bool read = true);
51−
52−
53−void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
54− QString logMessage = LogHandler::getInstance().printMessage((LogMsgType)type, context, message);
55−
56− if (!logMessage.isEmpty()) {
57−#ifdef Q_OS_WIN
58− OutputDebugStringA(logMessage.toLocal8Bit().constData());
59− OutputDebugStringA("\n");
60−#endif
61− if (logger) {
62− logger->addMessage(qPrintable(logMessage + "\n"));
63− }
64− }
65−}
66−
67−const char * LOG_FILTER_RULES = R"V0G0N(
68−hifi.gpu=true
69−)V0G0N";
70−
71−QString getRootPath() {
72− static std::once_flag once;
73− static QString result;
74− std::call_once(once, [&] {
75− QFileInfo file(__FILE__);
76− QDir parent = file.absolutePath();
77− result = QDir::cleanPath(parent.currentPath() + "/../../..");
78− });
79− return result;
80−}
81−
82−const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png";
83−const QString TEST_IMAGE_KTX = getRootPath() + "/scripts/developer/tests/cube_texture.ktx";
84−
85−int main(int argc, char** argv) {
86− QApplication app(argc, argv);
87− QCoreApplication::setApplicationName("KTX");
88− QCoreApplication::setOrganizationName("High Fidelity");
89− QCoreApplication::setOrganizationDomain("highfidelity.com");
90− logger.reset(new FileLogger());
91−
92− Q_ASSERT(ktx::evalPadding(0) == 0);
93− Q_ASSERT(ktx::evalPadding(1) == 3);
94− Q_ASSERT(ktx::evalPadding(2) == 2);
95− Q_ASSERT(ktx::evalPadding(3) == 1);
96− Q_ASSERT(ktx::evalPadding(4) == 0);
97− Q_ASSERT(ktx::evalPadding(1024) == 0);
98− Q_ASSERT(ktx::evalPadding(1025) == 3);
99− Q_ASSERT(ktx::evalPaddedSize(0) == 0);
100− Q_ASSERT(ktx::evalPaddedSize(1) == 4);
101− Q_ASSERT(ktx::evalPaddedSize(2) == 4);
102− Q_ASSERT(ktx::evalPaddedSize(3) == 4);
103− Q_ASSERT(ktx::evalPaddedSize(4) == 4);
104− Q_ASSERT(ktx::evalPaddedSize(1024) == 1024);
105− Q_ASSERT(ktx::evalPaddedSize(1025) == 1028);
106− Q_ASSERT(sizeof(ktx::Header) == 12 + (sizeof(uint32_t) * 13));
107−
108− DependencyManager::set<tracing::Tracer>();
109− qInstallMessageHandler(messageHandler);
110− QLoggingCategory::setFilterRules(LOG_FILTER_RULES);
111−
112− QImage image(TEST_IMAGE);
113− gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true);
114−
115− auto ktxMemory = gpu::Texture::serialize(*testTexture);
116− {
117− const auto& ktxStorage = ktxMemory->getStorage();
118− Q_ASSERT_X(ktx::KTX::validate(ktxStorage), __FUNCTION__, "KTX storage validation failed");
119− Q_ASSERT_X(ktxMemory->isValid(), __FUNCTION__, "KTX self-validation failed");
120− QSaveFile outFile(TEST_IMAGE_KTX);
121− if (!outFile.open(QFile::WriteOnly)) {
122− throw std::runtime_error("Unable to open file");
123− }
124− auto ktxSize = ktxStorage->size();
125− outFile.resize(ktxSize);
126− auto dest = outFile.map(0, ktxSize);
127− memcpy(dest, ktxStorage->data(), ktxSize);
128− outFile.unmap(dest);
129− outFile.commit();
130− }
131−
132− {
133− auto ktxFile = ktx::KTX::create(std::shared_ptr<storage::Storage>(new storage::FileStorage(TEST_IMAGE_KTX)));
134− {
135− const auto& memStorage = ktxMemory->getStorage();
136− const auto& fileStorage = ktxFile->getStorage();
137− Q_ASSERT(memStorage->size() == fileStorage->size());
138− Q_ASSERT(memStorage->data() != fileStorage->data());
139− Q_ASSERT(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size()));
140− Q_ASSERT(ktxFile->_images.size() == ktxMemory->_images.size());
141− auto imageCount = ktxFile->_images.size();
142− auto startMemory = ktxMemory->_storage->data();
143− auto startFile = ktxFile->_storage->data();
144− for (size_t i = 0; i < imageCount; ++i) {
145− auto memImages = ktxMemory->_images[i];
146− auto fileImages = ktxFile->_images[i];
147− Q_ASSERT(memImages._padding == fileImages._padding);
148− Q_ASSERT(memImages._numFaces == fileImages._numFaces);
149− Q_ASSERT(memImages._imageSize == fileImages._imageSize);
150− Q_ASSERT(memImages._faceSize == fileImages._faceSize);
151− Q_ASSERT(memImages._faceBytes.size() == memImages._numFaces);
152− Q_ASSERT(fileImages._faceBytes.size() == fileImages._numFaces);
153− auto faceCount = fileImages._numFaces;
154− for (uint32_t face = 0; face < faceCount; ++face) {
155− auto memFace = memImages._faceBytes[face];
156− auto memOffset = memFace - startMemory;
157− auto fileFace = fileImages._faceBytes[face];
158− auto fileOffset = fileFace - startFile;
159− Q_ASSERT(memOffset % 4 == 0);
160− Q_ASSERT(memOffset == fileOffset);
161− }
162− }
163− }
164− }
165− testTexture->setKtxBacking(TEST_IMAGE_KTX.toStdString());
166− return 0;
167−}
168−
169−#if 0
170−static const QString TEST_FOLDER { "H:/ktx_cacheold" };
171−//static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" };
172−
173−//static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" };
174−static const QString EXTENSIONS { "*.ktx" };
175−
176−int mainTemp(int, char**) {
177− qInstallMessageHandler(messageHandler);
178− auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS });
179− for (auto fileInfo : fileInfoList) {
180− qDebug() << fileInfo.filePath();
181− std::shared_ptr<storage::Storage> storage { new storage::FileStorage { fileInfo.filePath() } };
182−
183− if (!ktx::KTX::validate(storage)) {
184− qDebug() << "KTX invalid";
185− }
186−
187− auto ktxFile = ktx::KTX::create(storage);
188− ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor();
189−
190− qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs";
191− for (const auto& kv : ktxDescriptor.keyValues) {
192− qDebug() << "\t" << kv._key.c_str();
193− }
194−
195− auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
196− if (offsetToMinMipKV) {
197− auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV;
198− auto minMipLevelAvailable = *data;
199− qDebug() << "\tMin mip available " << minMipLevelAvailable;
200− assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels);
201− }
202− auto storageSize = storage->size();
203− for (const auto& faceImageDesc : ktxDescriptor.images) {
204− //assert(0 == (faceImageDesc._faceSize % 4));
205− for (const auto& faceOffset : faceImageDesc._faceOffsets) {
206− assert(0 == (faceOffset % 4));
207− auto faceEndOffset = faceOffset + faceImageDesc._faceSize;
208− assert(faceEndOffset <= storageSize);
209− }
210− }
211−
212− for (const auto& faceImage : ktxFile->_images) {
213− for (const ktx::Byte* faceBytes : faceImage._faceBytes) {
214− assert(0 == (reinterpret_cast<size_t>(faceBytes) % 4));
215− }
216− }
217− }
218− return 0;
219−}
220−#endif
221−
222−#include "main.moc"
223−
tests/networking/src/FileCacheTests.cpp+187−0
@@ -0,0 +1,187 @@
1+//
2+// ResoruceTests.cpp
3+//
4+// Copyright 2015 High Fidelity, Inc.
5+//
6+// Distributed under the Apache License, Version 2.0.
7+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
8+//
9+
10+
11+#include "FileCacheTests.h"
12+
13+#include <FileCache.h>
14+
15+QTEST_GUILESS_MAIN(FileCacheTests)
16+
17+using namespace cache;
18+
19+// Limit the file size to 10 MB
20+static const size_t MAX_UNUSED_SIZE { 1024 * 1024 * 10 };
21+static const QByteArray TEST_DATA { 1024 * 1024, '0' };
22+
23+static std::string getFileKey(int i) {
24+ return QString(QByteArray { 1, (char)i }.toHex()).toStdString();
25+}
26+
27+class TestFile : public File {
28+ using Parent = File;
29+public:
30+ TestFile(Metadata&& metadata, const std::string& filepath)
31+ : Parent(std::move(metadata), filepath) {
32+ }
33+};
34+
35+class TestFileCache : public FileCache {
36+ using Parent = FileCache;
37+
38+public:
39+ TestFileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr) : Parent(dirname, ext, nullptr) {
40+ initialize();
41+ }
42+
43+ std::unique_ptr<File> createFile(Metadata&& metadata, const std::string& filepath) override {
44+ qCInfo(file_cache) << "Wrote KTX" << metadata.key.c_str();
45+ return std::unique_ptr<File>(new TestFile(std::move(metadata), filepath));
46+ }
47+};
48+
49+using CachePointer = std::shared_ptr<TestFileCache>;
50+
51+// The FileCache relies on deleteLater to clear unused files, but QTest classes don't run a conventional event loop
52+// so we need to call this function to force any pending deletes to occur in the File destructor
53+static void forceDeletes() {
54+ while (QCoreApplication::hasPendingEvents()) {
55+ QCoreApplication::sendPostedEvents();
56+ QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
57+ QCoreApplication::processEvents();
58+ }
59+}
60+
61+size_t FileCacheTests::getCacheDirectorySize() const {
62+ size_t result = 0;
63+ QDir dir(_testDir.path());
64+ for (const auto& file : dir.entryList({ "*.tmp" })) {
65+ result += QFileInfo(dir.absoluteFilePath(file)).size();
66+ }
67+ return result;
68+}
69+
70+CachePointer makeFileCache(QString& location) {
71+ auto result = std::make_shared<TestFileCache>(location.toStdString(), "tmp");
72+ result->setMaxSize(MAX_UNUSED_SIZE);
73+ return result;
74+}
75+
76+void FileCacheTests::initTestCase() {
77+}
78+
79+void FileCacheTests::testUnusedFiles() {
80+ auto cache = makeFileCache(_testDir.path());
81+ std::list<FilePointer> inUseFiles;
82+
83+ {
84+ for (int i = 0; i < 100; ++i) {
85+ std::string key = getFileKey(i);
86+ auto file = cache->writeFile(TEST_DATA.data(), TestFileCache::Metadata(key, TEST_DATA.size()));
87+ inUseFiles.push_back(file);
88+ forceDeletes();
89+ QThread::msleep(10);
90+ }
91+ QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
92+ QCOMPARE(cache->getNumTotalFiles(), (size_t)100);
93+ // Release the in-use files
94+ inUseFiles.clear();
95+ // Cache state is updated, but the directory state is unchanged,
96+ // because the file deletes are triggered by an event loop
97+ QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
98+ QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
99+ QVERIFY(getCacheDirectorySize() > MAX_UNUSED_SIZE);
100+ forceDeletes();
101+ QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
102+ QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
103+ QVERIFY(getCacheDirectorySize() <= MAX_UNUSED_SIZE);
104+ }
105+
106+ // Reset the cache
107+ cache = makeFileCache(_testDir.path());
108+ {
109+ // Test files 0 to 89 are missing, because the LRU algorithm deleted them when we released the files
110+ for (int i = 0; i < 90; ++i) {
111+ std::string key = getFileKey(i);
112+ auto file = cache->getFile(key);
113+ QVERIFY(!file.get());
114+ }
115+
116+ // Test files 90 to 99 are present
117+ for (int i = 90; i < 100; ++i) {
118+ std::string key = getFileKey(i);
119+ auto file = cache->getFile(key);
120+ QVERIFY(file.get());
121+ inUseFiles.push_back(file);
122+
123+ if (i == 94) {
124+ // Each access touches the file, so we need to sleep here to ensure that the the last 5 files
125+ // have later times for cache ejection priority, otherwise the test runs too fast to reliably
126+ // differentiate
127+ QThread::msleep(1000);
128+ }
129+ }
130+
131+ QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
132+ QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
133+ inUseFiles.clear();
134+ QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
135+ QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
136+ }
137+}
138+
139+size_t FileCacheTests::getFreeSpace() const {
140+ return QStorageInfo(_testDir.path()).bytesFree();
141+}
142+
143+// FIXME if something else is changing the amount of free space on the target drive concurrently with this test
144+// running, then it may fail
145+void FileCacheTests::testFreeSpacePreservation() {
146+ QCOMPARE(getCacheDirectorySize(), MAX_UNUSED_SIZE);
147+ // Set the target free space to slightly above whatever the current free space is...
148+ size_t targetFreeSpace = getFreeSpace() + MAX_UNUSED_SIZE / 2;
149+
150+ // Reset the cache
151+ auto cache = makeFileCache(_testDir.path());
152+ // Setting the min free space causes it to eject the oldest files that cause the cache to exceed the minimum space
153+ cache->setMinFreeSize(targetFreeSpace);
154+ QVERIFY(getFreeSpace() < targetFreeSpace);
155+ forceDeletes();
156+ QCOMPARE(cache->getNumCachedFiles(), (size_t)5);
157+ QCOMPARE(cache->getNumTotalFiles(), (size_t)5);
158+ QVERIFY(getFreeSpace() >= targetFreeSpace);
159+ for (int i = 0; i < 95; ++i) {
160+ std::string key = getFileKey(i);
161+ auto file = cache->getFile(key);
162+ QVERIFY(!file.get());
163+ }
164+ for (int i = 95; i < 100; ++i) {
165+ std::string key = getFileKey(i);
166+ auto file = cache->getFile(key);
167+ QVERIFY(file.get());
168+ }
169+}
170+
171+void FileCacheTests::testWipe() {
172+ // Reset the cache
173+ auto cache = makeFileCache(_testDir.path());
174+ QCOMPARE(cache->getNumCachedFiles(), (size_t)5);
175+ QCOMPARE(cache->getNumTotalFiles(), (size_t)5);
176+ cache->wipe();
177+ QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
178+ QCOMPARE(cache->getNumTotalFiles(), (size_t)0);
179+ QVERIFY(getCacheDirectorySize() > 0);
180+ forceDeletes();
181+ QCOMPARE(getCacheDirectorySize(), (size_t)0);
182+}
183+
184+
185+void FileCacheTests::cleanupTestCase() {
186+}
187+
tests/networking/src/FileCacheTests.h+31−0
@@ -0,0 +1,31 @@
1+//
2+// ResourceTests.h
3+//
4+// Copyright 2015 High Fidelity, Inc.
5+//
6+// Distributed under the Apache License, Version 2.0.
7+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
8+//
9+
10+#ifndef hifi_ResourceTests_h
11+#define hifi_ResourceTests_h
12+
13+#include <QtTest/QtTest>
14+#include <QtCore/QTemporaryDir>
15+
16+class FileCacheTests : public QObject {
17+ Q_OBJECT
18+private slots:
19+ void initTestCase();
20+ void testUnusedFiles();
21+ void testFreeSpacePreservation();
22+ void cleanupTestCase();
23+ void testWipe();
24+
25+private:
26+ size_t getFreeSpace() const;
27+ size_t getCacheDirectorySize() const;
28+ QTemporaryDir _testDir;
29+};
30+
31+#endif // hifi_ResourceTests_h
tests/qt59/CMakeLists.txt+15−0
@@ -0,0 +1,15 @@
1+
2+set(TARGET_NAME qt59)
3+
4+if (WIN32)
5+ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217")
6+endif()
7+
8+# This is not a testcase -- just set it up as a regular hifi project
9+setup_hifi_project(Gui)
10+set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
11+
12+# link in the shared libraries
13+link_hifi_libraries(shared networking)
14+
15+package_libraries_for_deployment()
tests/qt59/src/main.cpp+78−0
@@ -0,0 +1,78 @@
1+//
2+// Created by Bradley Austin Davis on 2017/06/06
3+// Copyright 2013-2017 High Fidelity, Inc.
4+//
5+// Distributed under the Apache License, Version 2.0.
6+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
7+//
8+
9+#include <mutex>
10+
11+#include <QtCore/QCoreApplication>
12+
13+#include <NodeList.h>
14+#include <AccountManager.h>
15+#include <AddressManager.h>
16+#include <MessagesClient.h>
17+
18+#include <BuildInfo.h>
19+
20+
21+class Qt59TestApp : public QCoreApplication {
22+ Q_OBJECT
23+public:
24+ Qt59TestApp(int argc, char* argv[]);
25+ ~Qt59TestApp();
26+
27+private:
28+ void finish(int exitCode);
29+};
30+
31+
32+
33+Qt59TestApp::Qt59TestApp(int argc, char* argv[]) :
34+ QCoreApplication(argc, argv)
35+{
36+
37+ Setting::init();
38+ DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
39+ DependencyManager::set<AccountManager>([&] { return QString("Mozilla/5.0 (HighFidelityACClient)"); });
40+ DependencyManager::set<AddressManager>();
41+ DependencyManager::set<NodeList>(NodeType::Agent, 0);
42+ auto nodeList = DependencyManager::get<NodeList>();
43+ nodeList->startThread();
44+ auto messagesClient = DependencyManager::set<MessagesClient>();
45+ messagesClient->startThread();
46+ QTimer::singleShot(1000, [this] { finish(0); });
47+}
48+
49+Qt59TestApp::~Qt59TestApp() {
50+}
51+
52+
53+void Qt59TestApp::finish(int exitCode) {
54+ auto nodeList = DependencyManager::get<NodeList>();
55+
56+ // send the domain a disconnect packet, force stoppage of domain-server check-ins
57+ nodeList->getDomainHandler().disconnect();
58+ nodeList->setIsShuttingDown(true);
59+ nodeList->getPacketReceiver().setShouldDropPackets(true);
60+
61+ // remove the NodeList from the DependencyManager
62+ DependencyManager::destroy<NodeList>();
63+ QCoreApplication::exit(exitCode);
64+}
65+
66+
67+int main(int argc, char * argv[]) {
68+ QCoreApplication::setApplicationName("Qt59Test");
69+ QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION);
70+ QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
71+ QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
72+
73+ Qt59TestApp app(argc, argv);
74+
75+ return app.exec();
76+}
77+
78+#include "main.moc"
tests/render-perf/CMakeLists.txt+1−1
@@ -10,7 +10,7 @@ setup_hifi_project(Quick Gui OpenGL) · 9 unmodified lines
1010 set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
1111
1212 # link in the shared libraries
13−link_hifi_libraries(shared octree ktx gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics image)
13+link_hifi_libraries(shared octree ktx gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics image procedural)
1414
1515 package_libraries_for_deployment()
1616
tests/shaders/src/main.cpp+0−11
@@ -31,13 +31,9 @@ · 30 unmodified lines
3131 #include <render-utils/deferred_light_point_vert.h>
3232 #include <render-utils/deferred_light_spot_vert.h>
3333
34−#include <render-utils/directional_light_frag.h>
3534 #include <render-utils/directional_ambient_light_frag.h>
3635 #include <render-utils/directional_skybox_light_frag.h>
3736
38−#include <render-utils/point_light_frag.h>
39−#include <render-utils/spot_light_frag.h>
40−
4137 #include <render-utils/standardTransformPNTC_vert.h>
4238 #include <render-utils/standardDrawTexture_frag.h>
4339
@@ -66,9 +62,6 @@ · 22 unmodified lines
6662 #include <entities-renderer/textured_particle_frag.h>
6763 #include <entities-renderer/textured_particle_vert.h>
6864
69−#include <render-utils/hit_effect_vert.h>
70−#include <render-utils/hit_effect_frag.h>
71−
7265 #include <render-utils/overlay3D_vert.h>
7366 #include <render-utils/overlay3D_frag.h>
7467
@@ -155,11 +148,8 @@ void QTestWindow::draw() { · 80 unmodified lines
155148 testShaderBuild(simple_vert, simple_frag);
156149 testShaderBuild(simple_vert, simple_textured_frag);
157150 testShaderBuild(simple_vert, simple_textured_unlit_frag);
158− testShaderBuild(deferred_light_vert, directional_light_frag);
159151 testShaderBuild(deferred_light_vert, directional_ambient_light_frag);
160152 testShaderBuild(deferred_light_vert, directional_skybox_light_frag);
161− testShaderBuild(deferred_light_point_vert, point_light_frag);
162− testShaderBuild(deferred_light_spot_vert, spot_light_frag);
163153 testShaderBuild(standardTransformPNTC_vert, standardDrawTexture_frag);
164154 testShaderBuild(standardTransformPNTC_vert, DrawTextureOpaque_frag);
165155
@@ -190,7 +180,6 @@ void QTestWindow::draw() { · 24 unmodified lines
190180 testShaderBuild(ambient_occlusion_vert, ambient_occlusion_frag);
191181 testShaderBuild(ambient_occlusion_vert, occlusion_blend_frag);
192182 */
193− testShaderBuild(hit_effect_vert, hit_effect_frag);
194183
195184 testShaderBuild(overlay3D_vert, overlay3D_frag);
196185
tests/shared/src/CubicHermiteSplineTests.cpp+77−0
@@ -0,0 +1,77 @@
1+//
2+// CubicHermiteSplineTests.cpp
3+// tests/shared/src
4+//
5+// Copyright 2017 High Fidelity, Inc.
6+//
7+// Distributed under the Apache License, Version 2.0.
8+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
9+//
10+
11+#include "CubicHermiteSplineTests.h"
12+#include "../QTestExtensions.h"
13+#include <QtCore/QDebug>
14+#include "CubicHermiteSpline.h"
15+
16+QTEST_MAIN(CubicHermiteSplineTests)
17+
18+void CubicHermiteSplineTests::testCubicHermiteSplineFunctor() {
19+ glm::vec3 p0(0.0f, 0.0f, 0.0f);
20+ glm::vec3 m0(1.0f, 0.0f, 0.0f);
21+ glm::vec3 p1(1.0f, 1.0f, 0.0f);
22+ glm::vec3 m1(2.0f, 0.0f, 0.0f);
23+ CubicHermiteSplineFunctor hermiteSpline(p0, m0, p1, m1);
24+
25+ const float EPSILON = 0.0001f;
26+
27+ QCOMPARE_WITH_ABS_ERROR(p0, hermiteSpline(0.0f), EPSILON);
28+ QCOMPARE_WITH_ABS_ERROR(p1, hermiteSpline(1.0f), EPSILON);
29+
30+ // these values were computed offline.
31+ const glm::vec3 oneFourth(0.203125f, 0.15625f, 0.0f);
32+ const glm::vec3 oneHalf(0.375f, 0.5f, 0.0f);
33+ const glm::vec3 threeFourths(0.609375f, 0.84375f, 0.0f);
34+
35+ QCOMPARE_WITH_ABS_ERROR(oneFourth, hermiteSpline(0.25f), EPSILON);
36+ QCOMPARE_WITH_ABS_ERROR(oneHalf, hermiteSpline(0.5f), EPSILON);
37+ QCOMPARE_WITH_ABS_ERROR(threeFourths, hermiteSpline(0.75f), EPSILON);
38+}
39+
40+void CubicHermiteSplineTests::testCubicHermiteSplineFunctorWithArcLength() {
41+ glm::vec3 p0(0.0f, 0.0f, 0.0f);
42+ glm::vec3 m0(1.0f, 0.0f, 0.0f);
43+ glm::vec3 p1(1.0f, 1.0f, 0.0f);
44+ glm::vec3 m1(2.0f, 0.0f, 0.0f);
45+ CubicHermiteSplineFunctorWithArcLength hermiteSpline(p0, m0, p1, m1);
46+
47+ const float EPSILON = 0.001f;
48+
49+ float arcLengths[5] = {
50+ hermiteSpline.arcLength(0.0f),
51+ hermiteSpline.arcLength(0.25f),
52+ hermiteSpline.arcLength(0.5f),
53+ hermiteSpline.arcLength(0.75f),
54+ hermiteSpline.arcLength(1.0f)
55+ };
56+
57+ // these values were computed offline
58+ float referenceArcLengths[5] = {
59+ 0.0f,
60+ 0.268317f,
61+ 0.652788f,
62+ 1.07096f,
63+ 1.50267f
64+ };
65+
66+ QCOMPARE_WITH_ABS_ERROR(arcLengths[0], referenceArcLengths[0], EPSILON);
67+ QCOMPARE_WITH_ABS_ERROR(arcLengths[1], referenceArcLengths[1], EPSILON);
68+ QCOMPARE_WITH_ABS_ERROR(arcLengths[2], referenceArcLengths[2], EPSILON);
69+ QCOMPARE_WITH_ABS_ERROR(arcLengths[3], referenceArcLengths[3], EPSILON);
70+ QCOMPARE_WITH_ABS_ERROR(arcLengths[4], referenceArcLengths[4], EPSILON);
71+
72+ QCOMPARE_WITH_ABS_ERROR(0.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[0]), EPSILON);
73+ QCOMPARE_WITH_ABS_ERROR(0.25f, hermiteSpline.arcLengthInverse(referenceArcLengths[1]), EPSILON);
74+ QCOMPARE_WITH_ABS_ERROR(0.5f, hermiteSpline.arcLengthInverse(referenceArcLengths[2]), EPSILON);
75+ QCOMPARE_WITH_ABS_ERROR(0.75f, hermiteSpline.arcLengthInverse(referenceArcLengths[3]), EPSILON);
76+ QCOMPARE_WITH_ABS_ERROR(1.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[4]), EPSILON);
77+}
tests/shared/src/CubicHermiteSplineTests.h+23−0
@@ -0,0 +1,23 @@
1+//
2+// CubicHermiteSplineTests.h
3+// tests/shared/src
4+//
5+// Copyright 2017 High Fidelity, Inc.
6+//
7+// Distributed under the Apache License, Version 2.0.
8+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
9+//
10+
11+#ifndef hifi_CubicHermiteSplineTests_h
12+#define hifi_CubicHermiteSplineTests_h
13+
14+#include <QtTest/QtTest>
15+
16+class CubicHermiteSplineTests : public QObject {
17+ Q_OBJECT
18+private slots:
19+ void testCubicHermiteSplineFunctor();
20+ void testCubicHermiteSplineFunctorWithArcLength();
21+};
22+
23+#endif // hifi_TransformTests_h
tests/shared/src/GLMHelpersTests.cpp+74−0
@@ -140,3 +140,77 @@ void GLMHelpersTests::testSimd() { · 139 unmodified lines
140140 }
141141 qDebug() << "Done ";
142142 }
143+
144+void GLMHelpersTests::testGenerateBasisVectors() {
145+ { // very simple case: primary along X, secondary is linear combination of X and Y
146+ glm::vec3 u(1.0f, 0.0f, 0.0f);
147+ glm::vec3 v(1.0f, 1.0f, 0.0f);
148+ glm::vec3 w;
149+
150+ generateBasisVectors(u, v, u, v, w);
151+
152+ QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_X, EPSILON);
153+ QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_Y, EPSILON);
154+ QCOMPARE_WITH_ABS_ERROR(w, Vectors::UNIT_Z, EPSILON);
155+ }
156+
157+ { // point primary along Y instead of X
158+ glm::vec3 u(0.0f, 1.0f, 0.0f);
159+ glm::vec3 v(1.0f, 1.0f, 0.0f);
160+ glm::vec3 w;
161+
162+ generateBasisVectors(u, v, u, v, w);
163+
164+ QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_Y, EPSILON);
165+ QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_X, EPSILON);
166+ QCOMPARE_WITH_ABS_ERROR(w, -Vectors::UNIT_Z, EPSILON);
167+ }
168+
169+ { // pass bad data (both vectors along Y). The helper will guess X for secondary.
170+ glm::vec3 u(0.0f, 1.0f, 0.0f);
171+ glm::vec3 v(0.0f, 1.0f, 0.0f);
172+ glm::vec3 w;
173+
174+ generateBasisVectors(u, v, u, v, w);
175+
176+ QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_Y, EPSILON);
177+ QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_X, EPSILON);
178+ QCOMPARE_WITH_ABS_ERROR(w, -Vectors::UNIT_Z, EPSILON);
179+ }
180+
181+ { // pass bad data (both vectors along X). The helper will guess X for secondary, fail, then guess Y.
182+ glm::vec3 u(1.0f, 0.0f, 0.0f);
183+ glm::vec3 v(1.0f, 0.0f, 0.0f);
184+ glm::vec3 w;
185+
186+ generateBasisVectors(u, v, u, v, w);
187+
188+ QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_X, EPSILON);
189+ QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_Y, EPSILON);
190+ QCOMPARE_WITH_ABS_ERROR(w, Vectors::UNIT_Z, EPSILON);
191+ }
192+
193+ { // general case for arbitrary rotation
194+ float angle = 1.234f;
195+ glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f));
196+ glm::quat rotation = glm::angleAxis(angle, axis);
197+
198+ // expected values
199+ glm::vec3 x = rotation * Vectors::UNIT_X;
200+ glm::vec3 y = rotation * Vectors::UNIT_Y;
201+ glm::vec3 z = rotation * Vectors::UNIT_Z;
202+
203+ // primary is along x
204+ // secondary is linear combination of x and y
205+ // tertiary is unknown
206+ glm::vec3 u = 1.23f * x;
207+ glm::vec3 v = 2.34f * x + 3.45f * y;
208+ glm::vec3 w;
209+
210+ generateBasisVectors(u, v, u, v, w);
211+
212+ QCOMPARE_WITH_ABS_ERROR(u, x, EPSILON);
213+ QCOMPARE_WITH_ABS_ERROR(v, y, EPSILON);
214+ QCOMPARE_WITH_ABS_ERROR(w, z, EPSILON);
215+ }
216+}
tests/shared/src/GLMHelpersTests.h+1−0
@@ -21,6 +21,7 @@ private slots: · 20 unmodified lines
2121 void testEulerDecomposition();
2222 void testSixByteOrientationCompression();
2323 void testSimd();
24+ void testGenerateBasisVectors();
2425 };
2526
2627 float getErrorDifference(const float& a, const float& b);
tests/shared/src/StorageTests.cpp+9−7
@@ -8,6 +8,8 @@ · 7 unmodified lines
88
99 #include "StorageTests.h"
1010
11+#include <memory>
12+
1113 QTEST_MAIN(StorageTests)
1214
1315 using namespace storage;
@@ -32,8 +34,8 @@ void StorageTests::testConversion() { · 18 unmodified lines
3234 QFileInfo fileInfo(_testFile);
3335 QCOMPARE(fileInfo.exists(), false);
3436 }
35− StoragePointer storagePointer = std::make_unique<MemoryStorage>(_testData.size(), _testData.data());
36− QCOMPARE(storagePointer->size(), (quint64)_testData.size());
37+ StoragePointer storagePointer = std::unique_ptr<MemoryStorage>(new MemoryStorage(_testData.size(), _testData.data()));
38+ QCOMPARE(storagePointer->size(), _testData.size());
3739 QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0);
3840 // Convert to a file
3941 storagePointer = storagePointer->toFileStorage(_testFile);
@@ -42,12 +44,12 @@ void StorageTests::testConversion() { · 2 unmodified lines
4244 QCOMPARE(fileInfo.exists(), true);
4345 QCOMPARE(fileInfo.size(), (qint64)_testData.size());
4446 }
45− QCOMPARE(storagePointer->size(), (quint64)_testData.size());
47+ QCOMPARE(storagePointer->size(), _testData.size());
4648 QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0);
4749
4850 // Convert to memory
4951 storagePointer = storagePointer->toMemoryStorage();
50− QCOMPARE(storagePointer->size(), (quint64)_testData.size());
52+ QCOMPARE(storagePointer->size(), _testData.size());
5153 QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0);
5254 {
5355 // ensure the file is unaffected
@@ -58,13 +60,13 @@ void StorageTests::testConversion() { · 4 unmodified lines
5860
5961 // truncate the data as a new memory object
6062 auto newSize = _testData.size() / 2;
61− storagePointer = std::make_unique<MemoryStorage>(newSize, storagePointer->data());
62− QCOMPARE(storagePointer->size(), (quint64)newSize);
63+ storagePointer = std::unique_ptr<Storage>(new MemoryStorage(newSize, storagePointer->data()));
64+ QCOMPARE(storagePointer->size(), newSize);
6365 QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0);
6466
6567 // Convert back to file
6668 storagePointer = storagePointer->toFileStorage(_testFile);
67− QCOMPARE(storagePointer->size(), (quint64)newSize);
69+ QCOMPARE(storagePointer->size(), newSize);
6870 QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0);
6971 {
7072 // ensure the file is truncated
tools/ac-client/src/ACClientApp.cpp+1−14
@@ -90,23 +90,14 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : · 89 unmodified lines
9090
9191
9292 auto nodeList = DependencyManager::get<NodeList>();
93−
9493 // start the nodeThread so its event loop is running
95− QThread* nodeThread = new QThread(this);
96− nodeThread->setObjectName("NodeList Thread");
97− nodeThread->start();
98−
99− // make sure the node thread is given highest priority
100− nodeThread->setPriority(QThread::TimeCriticalPriority);
94+ nodeList->startThread();
10195
10296 // setup a timer for domain-server check ins
10397 QTimer* domainCheckInTimer = new QTimer(nodeList.data());
10498 connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
10599 domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
106100
107− // put the NodeList and datagram processing on the node thread
108− nodeList->moveToThread(nodeThread);
109−
110101 const DomainHandler& domainHandler = nodeList->getDomainHandler();
111102
112103 connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
@@ -239,12 +230,8 @@ void ACClientApp::finish(int exitCode) { · 126 unmodified lines
239230 // tell the packet receiver we're shutting down, so it can drop packets
240231 nodeList->getPacketReceiver().setShouldDropPackets(true);
241232
242− QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
243233 // remove the NodeList from the DependencyManager
244234 DependencyManager::destroy<NodeList>();
245− // ask the node thread to quit and wait until it is done
246− nodeThread->quit();
247− nodeThread->wait();
248235
249236 printFailedServers();
250237 QCoreApplication::exit(exitCode);
tools/atp-get/src/ATPGetApp.cpp+1−15
@@ -111,23 +111,13 @@ ATPGetApp::ATPGetApp(int argc, char* argv[]) : · 110 unmodified lines
111111
112112
113113 auto nodeList = DependencyManager::get<NodeList>();
114−
115− // start the nodeThread so its event loop is running
116− QThread* nodeThread = new QThread(this);
117− nodeThread->setObjectName("NodeList Thread");
118− nodeThread->start();
119−
120− // make sure the node thread is given highest priority
121− nodeThread->setPriority(QThread::TimeCriticalPriority);
114+ nodeList->startThread();
122115
123116 // setup a timer for domain-server check ins
124117 QTimer* domainCheckInTimer = new QTimer(nodeList.data());
125118 connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
126119 domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
127120
128− // put the NodeList and datagram processing on the node thread
129− nodeList->moveToThread(nodeThread);
130−
131121 const DomainHandler& domainHandler = nodeList->getDomainHandler();
132122
133123 connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
@@ -258,12 +248,8 @@ void ATPGetApp::finish(int exitCode) { · 124 unmodified lines
258248 // tell the packet receiver we're shutting down, so it can drop packets
259249 nodeList->getPacketReceiver().setShouldDropPackets(true);
260250
261− QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
262251 // remove the NodeList from the DependencyManager
263252 DependencyManager::destroy<NodeList>();
264− // ask the node thread to quit and wait until it is done
265− nodeThread->quit();
266− nodeThread->wait();
267253
268254 QCoreApplication::exit(exitCode);
269255 }
tools/oven/src/BakerCLI.cpp+64−0
@@ -0,0 +1,64 @@
1+//
2+// BakerCLI.cpp
3+// tools/oven/src
4+//
5+// Created by Robbie Uvanni on 6/6/17.
6+// Copyright 2017 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+
12+#include <QObject>
13+#include <QImageReader>
14+#include <QtCore/QDebug>
15+
16+#include "ModelBakingLoggingCategory.h"
17+#include "Oven.h"
18+#include "BakerCLI.h"
19+#include "FBXBaker.h"
20+#include "TextureBaker.h"
21+
22+BakerCLI::BakerCLI(Oven* parent) : QObject(parent) {
23+}
24+
25+void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) {
26+
27+ // if the URL doesn't have a scheme, assume it is a local file
28+ if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp") {
29+ inputUrl.setScheme("file");
30+ }
31+
32+ static const QString MODEL_EXTENSION { ".fbx" };
33+
34+ // check what kind of baker we should be creating
35+ bool isFBX = inputUrl.toDisplayString().endsWith(MODEL_EXTENSION, Qt::CaseInsensitive);
36+ bool isSupportedImage = false;
37+
38+ for (QByteArray format : QImageReader::supportedImageFormats()) {
39+ isSupportedImage |= inputUrl.toDisplayString().endsWith(format, Qt::CaseInsensitive);
40+ }
41+
42+ // create our appropiate baker
43+ if (isFBX) {
44+ _baker = std::unique_ptr<Baker> { new FBXBaker(inputUrl, outputPath, []() -> QThread* { return qApp->getNextWorkerThread(); }) };
45+ _baker->moveToThread(qApp->getFBXBakerThread());
46+ } else if (isSupportedImage) {
47+ _baker = std::unique_ptr<Baker> { new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath) };
48+ _baker->moveToThread(qApp->getNextWorkerThread());
49+ } else {
50+ qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl;
51+ QApplication::exit(1);
52+ }
53+
54+ // invoke the bake method on the baker thread
55+ QMetaObject::invokeMethod(_baker.get(), "bake");
56+
57+ // make sure we hear about the results of this baker when it is done
58+ connect(_baker.get(), &Baker::finished, this, &BakerCLI::handleFinishedBaker);
59+}
60+
61+void BakerCLI::handleFinishedBaker() {
62+ qCDebug(model_baking) << "Finished baking file.";
63+ QApplication::exit(_baker.get()->hasErrors());
64+}
tools/oven/src/BakerCLI.h+34−0
@@ -0,0 +1,34 @@
1+//
2+// BakerCLI.h
3+// tools/oven/src
4+//
5+// Created by Robbie Uvanni on 6/6/17.
6+// Copyright 2017 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+
12+#ifndef hifi_BakerCLI_h
13+#define hifi_BakerCLI_h
14+
15+#include <QtCore/QObject>
16+
17+#include "Baker.h"
18+#include "Oven.h"
19+
20+class BakerCLI : public QObject {
21+ Q_OBJECT
22+
23+public:
24+ BakerCLI(Oven* parent);
25+ void bakeFile(QUrl inputUrl, const QString outputPath);
26+
27+private slots:
28+ void handleFinishedBaker();
29+
30+private:
31+ std::unique_ptr<Baker> _baker;
32+};
33+
34+#endif // hifi_BakerCLI_h
tools/oven/src/Oven.cpp+30−7
@@ -11,16 +11,20 @@ · 10 unmodified lines
1111
1212 #include <QtCore/QDebug>
1313 #include <QtCore/QThread>
14+#include <QtCore/QCommandLineParser>
1415
1516 #include <image/Image.h>
1617 #include <SettingInterface.h>
1718
1819 #include "ui/OvenMainWindow.h"
19−
2020 #include "Oven.h"
21+#include "BakerCli.h"
2122
2223 static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/export";
2324
25+static const QString CLI_INPUT_PARAMETER = "i";
26+static const QString CLI_OUTPUT_PARAMETER = "o";
27+
2428 Oven::Oven(int argc, char* argv[]) :
2529 QApplication(argc, argv)
2630 {
@@ -30,24 +34,43 @@ Oven::Oven(int argc, char* argv[]) : · 3 unmodified lines
3034 // init the settings interface so we can save and load settings
3135 Setting::init();
3236
37+ // parse the command line parameters
38+ QCommandLineParser parser;
39+
40+ parser.addOptions({
41+ { CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" },
42+ { CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" }
43+ });
44+ parser.addHelpOption();
45+ parser.process(*this);
46+
3347 // enable compression in image library, except for cube maps
3448 image::setColorTexturesCompressionEnabled(true);
3549 image::setGrayscaleTexturesCompressionEnabled(true);
3650 image::setNormalTexturesCompressionEnabled(true);
3751 image::setCubeTexturesCompressionEnabled(true);
3852
39− // check if we were passed any command line arguments that would tell us just to run without the GUI
40−
41− // setup the GUI
42− _mainWindow = new OvenMainWindow;
43− _mainWindow->show();
44−
4553 // setup our worker threads
4654 setupWorkerThreads(QThread::idealThreadCount() - 1);
4755
4856 // Autodesk's SDK means that we need a single thread for all FBX importing/exporting in the same process
4957 // setup the FBX Baker thread
5058 setupFBXBakerThread();
59+
60+ // check if we were passed any command line arguments that would tell us just to run without the GUI
61+ if (parser.isSet(CLI_INPUT_PARAMETER) || parser.isSet(CLI_OUTPUT_PARAMETER)) {
62+ if (parser.isSet(CLI_INPUT_PARAMETER) && parser.isSet(CLI_OUTPUT_PARAMETER)) {
63+ BakerCLI* cli = new BakerCLI(this);
64+ cli->bakeFile(parser.value(CLI_INPUT_PARAMETER), parser.value(CLI_OUTPUT_PARAMETER));
65+ } else {
66+ parser.showHelp();
67+ QApplication::quit();
68+ }
69+ } else {
70+ // setup the GUI
71+ _mainWindow = new OvenMainWindow;
72+ _mainWindow->show();
73+ }
5174 }
5275
5376 Oven::~Oven() {
tools/oven/src/Oven.h+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
1414
1515 #include <QtWidgets/QApplication>
1616
17−#include <tbb/concurrent_vector.h>
17+#include <TBBHelpers.h>
1818
1919 #include <atomic>
2020
unpublishedScripts/marketplace/record/assets/sounds/countdown-tick.wavrenamed from scripts/system/assets/sounds/countdown-tick.wav+0−0

File renamed without changes.

unpublishedScripts/marketplace/record/assets/sounds/finish-recording.wavrenamed from scripts/system/assets/sounds/finish-recording.wav+0−0

File renamed without changes.

unpublishedScripts/marketplace/record/assets/sounds/start-recording.wavrenamed from scripts/system/assets/sounds/start-recording.wav+0−0

File renamed without changes.

unpublishedScripts/marketplace/record/html/css/edit-style.css+1262−0
@@ -0,0 +1,1262 @@
1+/*
2+// edit-style.css
3+//
4+// Created by Ryan Huffman on 13 Nov 2014
5+// Copyright 2014 High Fidelity, Inc.
6+//
7+// Distributed under the Apache License, Version 2.0.
8+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
9+*/
10+
11+@font-face {
12+ font-family: Raleway-Regular;
13+ src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */
14+ url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */
15+ url(../../../../interface/resources/fonts/Raleway-Regular.ttf), /* Development, running script in /HiFi/examples */
16+ url(../fonts/Raleway-Regular.ttf); /* Marketplace script */
17+}
18+
19+@font-face {
20+ font-family: Raleway-Light;
21+ src: url(../../../../resources/fonts/Raleway-Light.ttf),
22+ url(../../../../fonts/Raleway-Light.ttf),
23+ url(../../../../interface/resources/fonts/Raleway-Light.ttf),
24+ url(../fonts/Raleway-Light.ttf);
25+}
26+
27+@font-face {
28+ font-family: Raleway-Bold;
29+ src: url(../../../../resources/fonts/Raleway-Bold.ttf),
30+ url(../../../../fonts/Raleway-Bold.ttf),
31+ url(../../../../interface/resources/fonts/Raleway-Bold.ttf),
32+ url(../fonts/Raleway-Bold.ttf);
33+}
34+
35+@font-face {
36+ font-family: Raleway-SemiBold;
37+ src: url(../../../../resources/fonts/Raleway-SemiBold.ttf),
38+ url(../../../../fonts/Raleway-SemiBold.ttf),
39+ url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf),
40+ url(../fonts/Raleway-SemiBold.ttf);
41+}
42+
43+@font-face {
44+ font-family: FiraSans-SemiBold;
45+ src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf),
46+ url(../../../../fonts/FiraSans-SemiBold.ttf),
47+ url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf),
48+ url(../fonts/FiraSans-SemiBold.ttf);
49+}
50+
51+@font-face {
52+ font-family: AnonymousPro-Regular;
53+ src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf),
54+ url(../../../../fonts/AnonymousPro-Regular.ttf),
55+ url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf),
56+ url(../fonts/AnonymousPro-Regular.ttf);
57+}
58+
59+@font-face {
60+ font-family: HiFi-Glyphs;
61+ src: url(../../../../resources/fonts/hifi-glyphs.ttf),
62+ url(../../../../fonts/hifi-glyphs.ttf),
63+ url(../../../../interface/resources/fonts/hifi-glyphs.ttf),
64+ url(../fonts/hifi-glyphs.ttf);
65+}
66+
67+* {
68+ margin: 0;
69+ padding: 0;
70+}
71+
72+body {
73+ padding: 21px 21px 21px 21px;
74+
75+ color: #afafaf;
76+ background-color: #404040;
77+ font-family: Raleway-Regular;
78+ font-size: 15px;
79+
80+ -webkit-touch-callout: none;
81+ -webkit-user-select: none;
82+ -khtml-user-select: none;
83+ -moz-user-select: none;
84+ -ms-user-select: none;
85+ user-select: none;
86+
87+ overflow-x: hidden;
88+ overflow-y: auto;
89+}
90+
91+table {
92+ font-family: FiraSans-SemiBold;
93+ font-size: 15px;
94+ color: #afafaf;
95+ border-collapse: collapse;
96+ width: 100%;
97+ border: 2px solid #575757;
98+ border-radius: 7px;
99+}
100+
101+thead {
102+ font-family: Raleway-Regular;
103+ font-size: 12px;
104+ text-transform: uppercase;
105+ background-color: #1c1c1c;
106+ padding: 1px 0px;
107+ border-bottom: 1px solid #575757;
108+ width: 100%;
109+}
110+
111+tbody {
112+ width: 100%;
113+}
114+
115+tfoot {
116+ font-family: Raleway-Light;
117+ font-size: 13px;
118+ background-color: #1c1c1c;
119+ border-top: 1px solid #575757;
120+ width: 100%;
121+}
122+
123+tfoot tr {
124+ background-color: #1c1cff;
125+}
126+
127+thead tr {
128+ height: 26px; /* 28px with thead padding */
129+}
130+
131+thead th {
132+ height: 26px;
133+ background-color: #1c1c1c;
134+ border-right: 1px solid #575757;
135+}
136+
137+thead th:last-child {
138+ border: none;
139+}
140+
141+tbody td {
142+ height: 26px;
143+}
144+
145+tfoot td {
146+ height: 18px;
147+ width: 100%;
148+ background-color: #1c1c1c;
149+ margin-left: 12px;
150+}
151+
152+tr {
153+ width: 100%;
154+ cursor: pointer;
155+}
156+
157+tr:nth-child(odd) {
158+ background-color: #2e2e2e;
159+}
160+
161+tr:nth-child(even) {
162+ background-color: #1c1c1c;
163+}
164+
165+tr:focus {
166+ outline: none;
167+}
168+
169+tr.selected {
170+ color: #000000;
171+ background-color: #00b4ef;
172+}
173+
174+tr.selected + tr.selected {
175+ border-top: 1px solid #2e2e2e;
176+}
177+
178+th {
179+ text-align: center;
180+ word-wrap: nowrap;
181+ white-space: nowrap;
182+ padding-left: 12px;
183+ padding-right: 12px;
184+}
185+
186+td {
187+ overflow: hidden;
188+ text-overflow: ellipsis;
189+ white-space: nowrap;
190+ word-wrap: nowrap;
191+ padding-left: 12px;
192+ padding-right: 12px;
193+}
194+
195+td.url {
196+ white-space: nowrap;
197+ overflow: hidden;
198+}
199+
200+
201+input[type="text"], input[type="number"], textarea {
202+ margin: 0;
203+ padding: 0 12px;
204+ color: #afafaf;
205+ background-color: #252525;
206+ border: none;
207+ font-family: FiraSans-SemiBold;
208+ font-size: 15px;
209+}
210+
211+textarea {
212+ font-family: AnonymousPro-Regular;
213+ font-size: 16px;
214+ padding-top: 5px;
215+ padding-bottom: 5px;
216+ min-height: 64px;
217+ width: 100%;
218+ resize: vertical;
219+}
220+
221+input::-webkit-input-placeholder {
222+ font-style: italic;
223+}
224+
225+input:focus, textarea:focus {
226+ color: #fff;
227+ background-color: #000;
228+ outline: 1px solid #00b4ef;
229+ outline-offset: -1px;
230+}
231+
232+input::selection, textarea::selection {
233+ color: #000000;
234+ background-color: #00b4ef;
235+}
236+
237+input.search {
238+ border-radius: 14px;
239+}
240+
241+input.search:focus {
242+ outline: none;
243+ box-sizing: border-box;
244+ height: 26px;
245+ margin-top: 1px;
246+ margin-bottom: 1px;
247+ box-shadow: 0 0 0px 1px #00b4ef;
248+}
249+
250+input:disabled, textarea:disabled {
251+ background-color: #383838;
252+ color: #afafaf;
253+}
254+
255+input[type="text"] {
256+ height: 28px;
257+ width: 100%;
258+}
259+
260+input[type="number"] {
261+ position: relative;
262+ height: 28px;
263+ width: 124px;
264+}
265+
266+input[type=number] {
267+ padding-right: 3px;
268+}
269+input[type=number]::-webkit-inner-spin-button {
270+ -webkit-appearance: none;
271+ opacity: 1.0;
272+ display: block;
273+ position: relative;
274+ width: 10px;
275+ height: 100%;
276+ overflow: hidden;
277+ font-family: hifi-glyphs;
278+ font-size: 46px;
279+ color: #afafaf;
280+ cursor: pointer;
281+ /*background-color: #000000;*/
282+}
283+input[type=number]::-webkit-inner-spin-button:before,
284+input[type=number]::-webkit-inner-spin-button:after {
285+ position:absolute;
286+ left: -19px;
287+ line-height: 8px;
288+ text-align: center;
289+}
290+input[type=number]::-webkit-inner-spin-button:before {
291+ content: "6";
292+ top: 4px;
293+}
294+input[type=number]::-webkit-inner-spin-button:after {
295+ content: "5";
296+ bottom: 4px;
297+}
298+
299+input[type=number].hover-up::-webkit-inner-spin-button:before,
300+input[type=number].hover-down::-webkit-inner-spin-button:after {
301+ color: #ffffff;
302+}
303+
304+input.no-spin::-webkit-outer-spin-button,
305+input.no-spin::-webkit-inner-spin-button {
306+ display: none;
307+ -webkit-appearance: none;
308+ margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
309+ padding-right: 12px;
310+}
311+
312+input[type=button] {
313+ font-family: Raleway-Bold;
314+ font-size: 13px;
315+ text-transform: uppercase;
316+ vertical-align: top;
317+ height: 28px;
318+ min-width: 120px;
319+ padding: 0px 18px;
320+ margin-right: 6px;
321+ border-radius: 5px;
322+ border: none;
323+ color: #fff;
324+ background-color: #000;
325+ background: linear-gradient(#343434 20%, #000 100%);
326+ cursor: pointer;
327+}
328+
329+input[type=button].glyph {
330+ font-family: HiFi-Glyphs;
331+ font-size: 20px;
332+ text-transform: none;
333+ min-width: 32px;
334+ padding: 0;
335+}
336+
337+input[type=button].red {
338+ color: #fff;
339+ background-color: #94132e;
340+ background: linear-gradient(#d42043 20%, #94132e 100%);
341+}
342+input[type=button].blue {
343+ color: #fff;
344+ background-color: #1080b8;
345+ background: linear-gradient(#00b4ef 20%, #1080b8 100%);
346+}
347+input[type=button].white {
348+ color: #121212;
349+ background-color: #afafaf;
350+ background: linear-gradient(#fff 20%, #afafaf 100%);
351+}
352+
353+input[type=button]:enabled:hover {
354+ background: linear-gradient(#000, #000);
355+ border: none;
356+}
357+input[type=button].red:enabled:hover {
358+ background: linear-gradient(#d42043, #d42043);
359+ border: none;
360+}
361+input[type=button].blue:enabled:hover {
362+ background: linear-gradient(#00b4ef, #00b4ef);
363+ border: none;
364+}
365+input[type=button].white:enabled:hover {
366+ background: linear-gradient(#fff, #fff);
367+ border: none;
368+}
369+
370+input[type=button]:active {
371+ background: linear-gradient(#343434, #343434);
372+}
373+input[type=button].red:active {
374+ background: linear-gradient(#94132e, #94132e);
375+}
376+input[type=button].blue:active {
377+ background: linear-gradient(#1080b8, #1080b8);
378+}
379+input[type=button].white:active {
380+ background: linear-gradient(#afafaf, #afafaf);
381+}
382+
383+input[type=button]:disabled {
384+ color: #252525;
385+ background: linear-gradient(#575757 20%, #252525 100%);
386+}
387+
388+input[type=button][pressed=pressed] {
389+ color: #00b4ef;
390+}
391+
392+input[type=checkbox] {
393+ display: none;
394+}
395+input[type=checkbox] + label {
396+ padding-left: 24px;
397+ background-position-y: 6px;
398+ background-repeat: no-repeat;
399+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACpSURBVDhPY2xoaGD68+dPMSMjY9L////VgTQjAw4AlH8PxLOPHj1azWxjY1MBVNsBFBfBpwkEgNKcQGwtJyfHyATkF0KEiQdAzYlMQEIUyicFyDD9+/ePgRxMvsb///4zkIOZ/v0HmkAGHginYjGNGAzS+BpdkAj8mun/3//92DyPD//993cG88nTJ4+Zm5p/BSZeJYb/DEJADEzNOPF7hn8Mk69cvVIPAHN5pyfo70F5AAAAAElFTkSuQmCC);
400+}
401+input[type=checkbox]:enabled + label:hover {
402+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAClSURBVDhPY2hoaGD6//9/6b9//64D8T8gGycASr/7+/dv5/79+1kYgIxKqDjRAKiniRFIv2JgYBAFYlLAE0aQ66AckgDjjx8/yNP44cMH8jS+fPmSPI0PHz4kT+PNmzfJ03jp0iXyNJ46dYo8jYcPHyYnAbxm+vnzZz8wLhlIwd+/f5/BrKSkdExCQuLrnz9/lIBpUAiIQekXF34PTGmTT548WQ8AokXg+rhVtPYAAAAASUVORK5CYII=);
403+}
404+input[type=checkbox]:checked + label {
405+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFLSURBVDhPjZK9SgNBFIXvbCaQRDQq7mIhQRPBRisJKcwLWOobaCsExEaxcEEQe0trGysfwg0EwWoDsbFIJUaIBJOwus547saNP3FlPzgzzJxzL5edFbZtG77v7wkhtrXWS9gFRQC/DZ07jnOYKJfL+8ie4n7mvyIGdhpay+VyQuK8y5dPZoHuVtbpZcLi4wjJ1x4t316R9dDgBlsSi8mGu7pJjyJFzVaH+r7iqyHSELSQzVADjS0UgjlDKUUsLzVO98+9kSLGV5qaHXhjU0GWNSxk3hCIwnsfeMNCjTArLmHeUBodoLiE+R+jxuHPUZP4elGE3teonx2S/Q7lJzOUlkYQ+A4/xzyegzNhXmJpwTMXry9IFjcoa84O0r+QXpcK1cugCLREZadyoA19Ergxwf96nKjd1KqlYqmLQ540TUNwItUmRWdu3T36AODjwgpY9xqqAAAAAElFTkSuQmCC);
406+}
407+input[type=checkbox]:checked + label:hover {
408+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEySURBVDhPnZLPSsNAEMa/XVPBCE0RhNy0OarP4Av4AD6JB0GwVBA8efBBxHsgh4CQswcRoUIpiIpVAm3zZ5M4szFSbQPBH3xkJvNNZskOer2eLIriKM/ze1JOcS1UHmdZduF5ngEKjr/fN4Z6+oKerwA2gxC4HAFPEWVLsAzgZAvYt3Q6Enw6jg7uBAaTFMNwhpnKdbXCkAJdy8ROu4XrXW2HTJIErHcFDD6nC02Mom8PwymeE2gvS0ZRBBaTlsOXEmdlrfLLOI7Bakrl/zWxCT8T/904f9QW/b06qtrCUdtFCqdjYs2Q2jAPX8c2XQd7Kr/wfV8vwIPs4Ga1ixe5Xrr/YFLTYfKIvWzM6ZtwXZdX7lxXG0L+sxXHcW5t254opRzawQ0S72+dPmjTroIgOP0CQSMt5LDn1T8AAAAASUVORK5CYII=);
409+}
410+
411+.icon-input input {
412+ position: relative;
413+ padding-left: 36px;
414+}
415+.icon-input span {
416+ position: absolute;
417+ left: 6px;
418+ top: -2px;
419+ font-family: hifi-glyphs;
420+ font-size: 30px;
421+ color: #afafaf;
422+}
423+.icon-input input:focus + span {
424+ color: #ffffff;
425+}
426+
427+.selectable {
428+ -webkit-touch-callout: text;
429+ -webkit-user-select: text;
430+ -khtml-user-select: text;
431+ -moz-user-select: text;
432+ -ms-user-select: text;
433+ user-select: text;
434+ cursor: text;
435+}
436+
437+.color-box {
438+ display: inline-block;
439+ width: 15pt;
440+ height: 15pt;
441+ border: 0.75pt solid black;
442+ margin: 1.5pt;
443+ cursor: pointer;
444+}
445+
446+.color-box.highlight {
447+ width: 13.5pt;
448+ height: 13.5pt;
449+ border: 1.5pt solid black;
450+}
451+
452+
453+.section-header, .sub-section-header, hr {
454+ display: table;
455+ width: 100%;
456+ margin: 21px -21px 0 -21px;
457+ padding: 14px 21px 0 21px;
458+ font-family: Raleway-Regular;
459+ font-size: 12px;
460+ color: #afafaf;
461+ height: 28px;
462+ text-transform: uppercase;
463+ outline: none;
464+}
465+
466+.section-header {
467+ position: relative;
468+ background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAqCAIAAAAbNW1vAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAmSURBVChTY1BFAgzhSIDBAQmMcoYHRwIJMCgjAQZ9JMBgBQdWVgBh5XmBV5A2FQAAAABJRU5ErkJggg==) repeat-x top left;
469+}
470+
471+.sub-section-header, .no-collapse, hr {
472+ background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left;
473+}
474+
475+.section-header:first-child {
476+ margin-top: -2px;
477+ padding-top: 0;
478+ background: none;
479+ height: auto;
480+}
481+
482+.sub-section-header {
483+ margin-bottom: -10px;
484+}
485+
486+.section-header span {
487+ font-family: HiFi-Glyphs;
488+ font-size: 30px;
489+ float: right;
490+ position: absolute;
491+ top: 4px;
492+ right: 13px;
493+}
494+
495+.section-header[collapsed="true"] {
496+ margin-bottom: -21px;
497+}
498+
499+hr {
500+ border: none;
501+ padding-top: 2px;
502+}
503+
504+.text-group[collapsed="true"] ~ .text-group,
505+.zone-group[collapsed="true"] ~ .zone-group,
506+.web-group[collapsed="true"] ~ .web-group,
507+.hyperlink-group[collapsed="true"] ~ .hyperlink-group,
508+.spatial-group[collapsed="true"] ~ .spatial-group,
509+.physical-group[collapsed="true"] ~ .physical-group,
510+.behavior-group[collapsed="true"] ~ .behavior-group,
511+.model-group[collapsed="true"] ~ .model-group,
512+.light-group[collapsed="true"] ~ .light-group {
513+ display: none !important;
514+}
515+
516+
517+.property {
518+ display: table;
519+ width: 100%;
520+ margin-top: 21px;
521+ min-height: 28px;
522+}
523+
524+.property.checkbox {
525+ width: auto;
526+}
527+
528+.property label, .number label {
529+ display: table-cell;
530+ vertical-align: middle;
531+ font-family: Raleway-SemiBold;
532+ font-size: 14px;
533+}
534+.property label .unit, .number label .unit {
535+ margin-left: 8px;
536+ font-family: Raleway-Light;
537+ font-size: 13px;
538+}
539+
540+.value {
541+ display: block;
542+ min-height: 18px;
543+}
544+.value label {
545+ display: inline-block;
546+ vertical-align: top;
547+ width: 48px;
548+}
549+.value span {
550+ font-family: FiraSans-SemiBold;
551+ font-size: 15px;
552+}
553+
554+.checkbox + .checkbox {
555+ margin-top: 0;
556+}
557+
558+.checkbox-sub-props {
559+ margin-top: 18px;
560+}
561+
562+.property .number {
563+ float: left;
564+}
565+.property .number + .number {
566+ margin-left: 10px;
567+}
568+
569+.text label, .url label, .number label, .textarea label, .rgb label, .xyz label, .pyr label, .dropdown label, .gen label {
570+ float: left;
571+ margin-left: 1px;
572+ margin-bottom: 3px;
573+ margin-top: -2px;
574+}
575+
576+.number > input {
577+ clear: both;
578+ float: left;
579+}
580+.number > span {
581+ clear: both;
582+ float: left;
583+}
584+.xyz > div, .pyr > div, .gen > div {
585+ clear: both;
586+}
587+
588+.dropdown {
589+ position: relative;
590+ margin-bottom: -17px;
591+}
592+
593+.dropdown select {
594+ clear: both;
595+}
596+
597+.dropdown dl {
598+ clear: both;
599+}
600+.dropdown dl {
601+ font-family: FiraSans-SemiBold;
602+ font-size: 15px;
603+ width: 292px;
604+ height: 28px;
605+ padding: 0 28px 0 12px;
606+ color: #afafaf;
607+ background: linear-gradient(#7d7d7d 20%, #686a68 100%);
608+ position: relative;
609+}
610+.dropdown dl[dropped="true"] {
611+ color: #404040;
612+ background: linear-gradient(#afafaf, #afafaf);
613+}
614+
615+.dropdown dt {
616+ height: 100%;
617+ box-sizing: border-box;
618+ border-right: 1px solid #121212;
619+ width: 100%;
620+}
621+.dropdown dt:hover {
622+ color: #404040;
623+}
624+.dropdown dt:focus {
625+ outline: none;
626+}
627+.dropdown dt span:first-child {
628+ display: inline-block;
629+ position: relative;
630+ top: 5px;
631+}
632+.dropdown dt span:last-child {
633+ font-family: HiFi-Glyphs;
634+ font-size: 42px;
635+ float: right;
636+ margin-right: -48px;
637+ position: relative;
638+ left: -12px;
639+ top: -9px;
640+}
641+
642+.dropdown dd {
643+ position: absolute;
644+ top: 28px;
645+ left: 3px;
646+ display: none;
647+}
648+.dropdown dl[dropped="true"] dd {
649+ display: block;
650+}
651+
652+.dropdown li {
653+ list-style-type: none;
654+ padding: 3px 0 1px 12px;
655+ width: 320px;
656+ height: auto;
657+ font-family: FiraSans-SemiBold;
658+ font-size: 15px;
659+ color: #404040;
660+ background-color: #afafaf
661+}
662+.dropdown li:hover {
663+ background-color: #00b4ef;
664+}
665+
666+.dropdown dl[disabled="disabled"], .dropdown dl[disabled="disabled"][dropped="true"] {
667+ color: #252525;
668+ background: linear-gradient(#575757 20%, #252525 100%);
669+}
670+.dropdown dl[disabled="disabled"] dd {
671+ display: none;
672+}
673+.dropdown dl[disabled="disabled"] dt:hover {
674+ color: #252525;
675+}

587 more lines aren’t shown here.

unpublishedScripts/marketplace/record/html/css/record.cssrenamed from scripts/system/html/css/record.css+0−0

File renamed without changes.

unpublishedScripts/marketplace/record/html/fonts/FiraSans-SemiBold.ttf+0−0

Binary file not shown.

unpublishedScripts/marketplace/record/html/fonts/FiraSans.license+94−0

This diff is large: its 95 lines aren’t shown here.

unpublishedScripts/marketplace/record/html/fonts/Raleway-Bold.ttf+0−0

Binary file not shown.

unpublishedScripts/marketplace/record/html/fonts/Raleway-Light.ttf+0−0

Binary file not shown.

unpublishedScripts/marketplace/record/html/fonts/Raleway-Regular.ttf+0−0

Binary file not shown.

unpublishedScripts/marketplace/record/html/fonts/Raleway-SemiBold.ttf+0−0

Binary file not shown.

unpublishedScripts/marketplace/record/html/fonts/Raleway.license+94−0

This diff is large: its 95 lines aren’t shown here.

unpublishedScripts/marketplace/record/html/fonts/hifi-glyphs.ttf+0−0

Binary file not shown.

unpublishedScripts/marketplace/record/html/img/loader-red-countdown-ring.gifrenamed from scripts/system/html/img/loader-red-countdown-ring.gif+0−0

File renamed without changes.

unpublishedScripts/marketplace/record/html/js/record.jsrenamed from scripts/system/html/js/record.js+0−0

File renamed without changes.

unpublishedScripts/marketplace/record/html/record.htmlrenamed from scripts/system/html/record.html+0−0

File renamed without changes.

unpublishedScripts/marketplace/record/playRecordingAC.jsrenamed from scripts/system/playRecordingAC.js+0−0

File renamed without changes.

unpublishedScripts/marketplace/record/record.jsrenamed from scripts/system/record.js+0−0

File renamed without changes.

unpublishedScripts/marketplace/stopwatch/models/transparent-box.fbx+0−0

Binary file not shown.

unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js+36−2

This diff is large: its 50 lines aren’t shown here.

unpublishedScripts/marketplace/stopwatch/stopwatchReset.js+22−0

This diff is large: its 23 lines aren’t shown here.

unpublishedScripts/marketplace/stopwatch/stopwatchServer.js+31−19

This diff is large: its 102 lines aren’t shown here.

unpublishedScripts/marketplace/stopwatch/stopwatchStartStop.jsrenamed from unpublishedScripts/marketplace/stopwatch/stopwatchClient.js+7−6

This diff is large: its 28 lines aren’t shown here.