<?xml version='1.0' encoding='UTF-8'?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-5295746446529817470</atom:id><lastBuildDate>Sun, 09 Aug 2026 08:08:03 +0000</lastBuildDate><category>dotnetmag</category><category>HoloLens</category><category>UWP</category><category>wpdev</category><category>Unity3D</category><category>Windows Mixed Reality</category><category>WP7NL</category><category>Windows Phone 7</category><category>WP7</category><category>XAML</category><category>behavior</category><category>C#</category><category>wpnl</category><category>MVVM</category><category>MVVM Light</category><category>Windows Phone 8</category><category>Mapping</category><category>Windows 8</category><category>HoloLens2</category><category>MRTK2</category><category>Windows 10</category><category>Silverlight</category><category>Unity</category><category>Win8nl</category><category>Azure</category><category>Windows Phone</category><category>Windows phone 8.1</category><category>Architecture</category><category>Off-topic</category><category>WinRt</category><category>Extension methods</category><category>windev</category><category>IoT</category><category>DataBinding</category><category>Raspberry PI2</category><category>Xamarin Forms</category><category>JSON</category><category>Unit Test</category><category>Web Services</category><category>Javascript</category><category>Universal Apps</category><category>WCF</category><category>ASP.NET</category><category>ASP.NET AJAX</category><category>Application Bar</category><category>Bing Map Control</category><category>Blend</category><category>Serialization</category><category>Service</category><category>Xamarin</category><category>Angularjs</category><category>Augmented Reality</category><category>Bluetooth</category><category>CSLA</category><category>Converter</category><category>Custom Vision</category><category>Generics</category><category>NFC</category><category>Reactive</category><category>Tombstoning</category><category>TypeScript</category><category>UX</category><category>XML</category><category>rant</category><category>refactoring</category><category>reflection</category><category>windows 8.1</category><category>.NET 4.0</category><category>ARM</category><category>Attached Dependency Property</category><category>Behaviour</category><category>Compress</category><category>Emulator</category><category>Functional programming</category><category>GZip</category><category>Lambda expressions</category><category>Marketplace</category><category>Microsoft Band</category><category>Microsoft Store</category><category>Mono</category><category>Proxy</category><category>Rx</category><category>Silverlight 3.0</category><category>Silverlight 4.0</category><category>Visual Studio</category><category>html</category><category>Artificial Intelligence</category><category>Bing Search API</category><category>C# 3.0</category><category>Debugging</category><category>Dependency Property</category><category>HTTP</category><category>IL2CPP</category><category>Kinect</category><category>MAT</category><category>MRTK-vNext</category><category>Machine Learning</category><category>Mango</category><category>Metro</category><category>ONNX</category><category>OWIN</category><category>PCL</category><category>PresentationCore</category><category>REST</category><category>Shared Code</category><category>Signal/R</category><category>Silverlight 2.0</category><category>Spatial Sound</category><category>Universal App</category><category>Visual Studio 2008</category><category>Visual Studio 2010</category><category>Visual Studio 2015</category><category>Visual Studio 2019</category><category>WIX</category><category>WebApi</category><category>Windows ML</category><category>Windows Machine Learning</category><category>Windows Store</category><category>XPlat</category><category>inheritance</category><category>root</category><category>string</category><category>.NET 3.5</category><category>.NET 3.5SP1</category><category>.NET Native</category><category>.NET Service Bus</category><category>3D</category><category>ADO.NET</category><category>Active Directory</category><category>Android</category><category>Anonymous access</category><category>AppStudio</category><category>Azure DevOps</category><category>Azure Tables</category><category>CGI</category><category>Client Object Model</category><category>Compact Framework</category><category>Continuous Deployment</category><category>Custom Taks</category><category>DataField</category><category>DataForm</category><category>DateTime</category><category>Enterprise Library</category><category>Enum</category><category>Enumeration</category><category>Facebook</category><category>Fody</category><category>Full Trust</category><category>Geofencing</category><category>HTTP version</category><category>Hosting in IIS</category><category>Immersive apps</category><category>Implicit properties</category><category>ImportMany</category><category>Insiders</category><category>Live Tile</category><category>Lumia800</category><category>MEF</category><category>Managed Extensibility Framework</category><category>Master Page</category><category>MediaPermission</category><category>Message Queue</category><category>Mockup</category><category>Mogade</category><category>NUI</category><category>Native code</category><category>Natural User Interfaces</category><category>Nokia</category><category>NuGet</category><category>Object initialization</category><category>OpenLayers</category><category>Oracle</category><category>PowerShell</category><category>Relative path</category><category>Running from Visual Studio</category><category>Security</category><category>Serializable</category><category>Sharepoint</category><category>Sharepoint 2010</category><category>Slider</category><category>Submission</category><category>System.Windows.Media.Imaging</category><category>Triggers</category><category>Visual State Manager</category><category>WMS</category><category>WPF</category><category>Web role</category><category>Windows Mobile</category><category>async</category><category>backslash</category><category>color conversion</category><category>css</category><category>delegates</category><category>deprecated</category><category>external program</category><category>global</category><category>iOS</category><category>image cutting</category><category>image url</category><category>jquery</category><category>jquery mobile</category><category>knockout</category><category>namespace clash</category><category>oAuth</category><category>obsolete</category><category>rgb</category><category>root path</category><category>same directory</category><category>sockets</category><category>stored function</category><category>stored procedure</category><category>touch</category><category>wearables</category><title>Dotnet by Example</title><description>&lt;b&gt;Motto: &quot;Talk is cheap, show me the code!&quot; &lt;/b&gt;&lt;br&gt;&#xa;&lt;br&gt;&#xa;This blog attempts to be a collection of how-to examples in the Microsoft software stack - things that may take forever to find out, especially for the beginner. I see it as my way to return something to the Microsoft community in exchange for what I learned from it. </description><link>http://dotnetbyexample.blogspot.com/</link><managingEditor>noreply@blogger.com (Joost van Schaik)</managingEditor><generator>Blogger</generator><openSearch:totalResults>355</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-2331892829328257259</guid><pubDate>Wed, 29 Jul 2020 08:36:00 +0000</pubDate><atom:updated>2020-07-29T10:38:30.641+02:00</atom:updated><title>Service announcement - this blog has moved on</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;In November 2007, I started my journey into blogging. Little did I know I was taking the first step that would ultimately made me a Microsoft MVP, took me several times to the campus in Redmond, and even put me on the road to &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; development. Back then I took the easy and cheap route and opted for &lt;a href=&quot;http://blogger.com&quot; target=&quot;_blank&quot;&gt;Blogger&lt;/a&gt;, then an independent company. Unfortunately, the company was later acquired by Google and after that the platform did not get much love anymore. The online editor, at the time great, became more cumbersome to use over time. Then came LiveWriter, part of Microsoft Essentials, and suddenly I could blog in a WYSIWIG Word style editor. This made life &lt;em&gt;so&lt;/em&gt; much easier. &lt;/p&gt;&lt;p&gt;But Live Essentials was slowly being phased out, and little by little, Live Writer was also sunsetting. But in the end of 2015 the product was given a new leash on life by being rechristened as &quot;Open Live Writer&quot;. This was especially important as Google changed the Blogger API regularly. When it became &lt;a href=&quot;https://www.microsoft.com/en-us/p/open-live-writer/9nblggh5279m&quot; target=&quot;_blank&quot;&gt;a Store app&lt;/a&gt;, life became even easier: it got updated automatically!&lt;/p&gt;&lt;p&gt;That is... for the time enthusiasts were still willing to maintain it. &lt;a href=&quot;https://github.com/OpenLiveWriter/OpenLiveWriter&quot; target=&quot;_blank&quot;&gt;The repo associated with Open Live Writer&lt;/a&gt; has been showing little activity the last year. Also the Blogger platform, having received very little love by Google, has lost a lot of usage, to such an extent that repos for blogging tools &lt;a href=&quot;https://github.com/RickStrahl/MarkdownMonster/issues/197&quot; target=&quot;_blank&quot;&gt;simply close down issues asking for Blogger support&lt;/a&gt; calling it &quot;a bunch of work for little payback&quot;. And this comment is from 2017.&lt;/p&gt;&lt;p&gt;Recently I had my laptop reinstalled, and when I wanted to re-configure Open Live Writer again, I was greeted with this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;225&quot; src=&quot;https://www.schaikweb.net/blog/20200927/GoogleError.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;At this point, I had enough. After almost 13 years jumping through several hoops to keep this puppy alive, it was time to move on. &lt;/p&gt;&lt;h2&gt;DotNetByExample - The Next Generation&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://localjoost.github.io/&quot; target=&quot;_blank&quot;&gt;&lt;img width=&quot;640&quot; height=&quot;369&quot; src=&quot;https://www.schaikweb.net/blog/20200927/NextGen.png&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;And here we are. I have copied over all the content to my &lt;a href=&quot;https://localjoost.github.io/&quot; target=&quot;_blank&quot;&gt;new home on the web&lt;/a&gt;, using GitHub pages, after consulting with my dear friend &lt;a href=&quot;https://twitter.com/qmatteoq&quot; target=&quot;_blank&quot;&gt;Matteo Pagani&lt;/a&gt;. This is the provisionary look &amp;amp; feel, there is still a lot I don&#39;t like, but at least it&#39;s accessible and maintainable for me again. I also wrote a little tool that updates all links to previous blogposts inside the imported pages, and even generates a JavaScript snippet that redirects all links to the migrated articles in the new blog. &lt;/p&gt;&lt;p&gt;So after 13 years, it&#39;s time to leave this spot that has been my home on the web for so long. I hope you will all join me on my continuing journey in development!&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2020/07/service-announcement-this-blog-is-being.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>2</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-8584611115573230413</guid><pubDate>Wed, 22 Jul 2020 13:48:00 +0000</pubDate><atom:updated>2020-07-22T15:49:35.618+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">Unity3D</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRKT2 - NuGet for Unity crashes when using MRTK2</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;Recently I was setting up a new &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 2 project. I usually take the following route:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Create a Unity project with the recommended version for the current MRTK (I use 2.4 now, so that is 2018.4.20f1)&lt;/li&gt;&lt;li&gt;Switch build platform to UWP&lt;/li&gt;&lt;li&gt;Import the &lt;a href=&quot;https://www.schaikweb.net/blog/20200603/01_pointeroptionsx.png&quot; target=&quot;_blank&quot;&gt;MRTK2&lt;/a&gt; as source&lt;/li&gt;&lt;li&gt;Add the MRKT2 to the scene&lt;/li&gt;&lt;li&gt;Apply optimization settings&lt;/li&gt;&lt;li&gt;Select the right sound Spatializer (MS HRTF Spatializer)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;And then, since recently, I add NuGet for Unity. &lt;/p&gt;&lt;h2&gt;Something is rotten in de state of NuGet&lt;/h2&gt;&lt;p&gt;Now this comes a nice UnityPackage that you can &lt;a href=&quot;https://github.com/GlitchEnzo/NuGetForUnity/releases&quot; target=&quot;_blank&quot;&gt;download from here&lt;/a&gt;. Currently it&#39;s at 2.0.1. It works fine in plain Unity projects. It works also fine in UWP build. But if you follow the workflow I show above, it breaks. If you open the NuGet package manager, in stead of the UI you expect, you will see only this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;272&quot; height=&quot;480&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200722/01_error.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And you will see these errors in the console:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;System.TypeInitializationException: The type initializer for &#39;NugetForUnity.NugetHelper&#39; threw an exception. ---&amp;gt; System.NullReferenceException: Object reference not set to an instance of an object&lt;br&gt;&amp;nbsp;&amp;nbsp; at NugetForUnity.NugetHelper.LoadNugetConfigFile () [0x0011c] in &amp;lt;b7bde984cef1447da61a3fd28d4789b0&amp;gt;:0 &lt;br&gt;&amp;nbsp;&amp;nbsp; at NugetForUnity.NugetHelper..cctor () [0x001e6] in &amp;lt;b7bde984cef1447da61a3fd28d4789b0&amp;gt;:0&lt;/li&gt;&lt;li&gt;NullReferenceException: Object reference not set to an instance of an object&lt;br&gt;
NugetForUnity.NugetHelper.LoadNugetConfigFile () (at &amp;lt;b7bde984cef1447da61a3fd28d4789b0&amp;gt;:0)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Great. Now what?&lt;/p&gt;&lt;h2&gt;The culprit&lt;/h2&gt;&lt;p&gt;When you import the the MRTK2, at some point a NuGet.config file is created. This sits in the Assets root and looks like this:&lt;/p&gt;&lt;pre style=&quot;font-size: 11px;&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;configuration&amp;gt;
  &amp;lt;packageSources&amp;gt;
    &amp;lt;add key=&quot;MSBuildForUnity&quot; value=&quot;https://pkgs.dev.azure.com/UnityDeveloperTools/MSBuildForUnity/_packaging/UnityDeveloperTools/nuget/v3/index.json&quot; /&amp;gt;
  &amp;lt;/packageSources&amp;gt;
&amp;lt;/configuration&lt;/pre&gt;&lt;p&gt;For some reason, NuGet for Unity does not get very happy about that. If you quit Unity, delete NuGet.config and the NuGet.config.meta, start again and open the NuGet package manager again, a new NuGet.config is created that looks like &lt;em&gt;this:&lt;/em&gt;&lt;/p&gt;&lt;pre style=&quot;font-size: 11px;&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;configuration&amp;gt;
    &amp;lt;packageSources&amp;gt;
       &amp;lt;add key=&quot;NuGet&quot; value=&quot;http://www.nuget.org/api/v2/&quot; /&amp;gt;
    &amp;lt;/packageSources&amp;gt;
    &amp;lt;disabledPackageSources /&amp;gt;
    &amp;lt;activePackageSource&amp;gt;
       &amp;lt;add key=&quot;All&quot; value=&quot;(Aggregate source)&quot; /&amp;gt;
    &amp;lt;/activePackageSource&amp;gt;
    &amp;lt;config&amp;gt;
       &amp;lt;add key=&quot;repositoryPath&quot; value=&quot;./Packages&quot; /&amp;gt;
       &amp;lt;add key=&quot;DefaultPushSource&quot; value=&quot;http://www.nuget.org/api/v2/&quot; /&amp;gt;
    &amp;lt;/config&amp;gt;
&amp;lt;/configuration&lt;/pre&gt;&lt;p&gt;and if you &lt;em&gt;now&lt;/em&gt; click select the NuGet package manger again, it starts up normally.&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200722/02_ok.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;If tried to add the MSBuildForUnity to the package sources like this, because maybe the MRTK2 needs it for something:&lt;/p&gt;&lt;pre style=&quot;font-size: 11px;&quot;&gt;&amp;lt;packageSources&amp;gt;
   &amp;lt;add key=&quot;NuGet&quot; value=&quot;http://www.nuget.org/api/v2/&quot; /&amp;gt;
   &amp;lt;add key=&quot;MSBuildForUnity&quot; value=&quot;https://pkgs.dev.azure.com/UnityDeveloperTools/MSBuildForUnity/_packaging/UnityDeveloperTools/nuget/v3/index.json&quot; /&amp;gt;
&amp;lt;/packageSources&lt;/pre&gt;&lt;p&gt;but all that does is generate another error:&lt;/p&gt;&lt;p&gt;&quot;Unable to retrieve package list from https://pkgs.dev.azure.com/UnityDeveloperTools/MSBuildForUnity/_packaging/UnityDeveloperTools/nuget/v3/index.jsonSearch()?$filter=IsLatestVersion&amp;amp;$orderby=DownloadCount desc&amp;amp;$skip=0&amp;amp;$top=15&amp;amp;searchTerm=&#39;&#39;&amp;amp;targetFramework=&#39;&#39;&amp;amp;includePrerelease=false&lt;br&gt;
System.Net.WebException: The remote server returned an error: (404) Not Found.&quot;&lt;/p&gt;&lt;p&gt;So I actually have no idea why this NuGet.config is created or how. But it definitely needs to go, apparently. &lt;/p&gt;&lt;h2&gt;The short version&lt;/h2&gt;&lt;p&gt;Before importing NuGet for Unity, check for an existing NuGet.config and NuGet.config.meta and delete those; your NuGet package manager will then work as planned.&lt;/p&gt;&lt;p&gt;No code today, because all I wrote today here boils down to the one sentence above :)&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2020/07/migrating-for-mrkt2-nuget-for-unity.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-6789127036437622594</guid><pubDate>Tue, 23 Jun 2020 10:54:00 +0000</pubDate><atom:updated>2020-06-24T11:32:24.712+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity3D</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRTK2: Changing viewpoint rotation angle when pushing the thumbstick</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;&lt;img width=&quot;240&quot; height=&quot;165&quot; align=&quot;right&quot; style=&quot;margin: 0px 0px 0px 7px; border: 0px currentcolor; border-image: none; float: right; display: inline; background-image: none;&quot; src=&quot;https://microsoft.github.io/MixedRealityToolkit-Unity/Documentation/Images/Pointers/MRTK_Pointers_Parabolic.png&quot; border=&quot;0&quot;&gt;A short one, but one that took me &lt;em&gt;quite&lt;/em&gt; some time to find.&lt;/p&gt;&lt;p&gt;If you are using &lt;a href=&quot;https://www.schaikweb.net/blog/20200603/01_pointeroptionsx.png&quot; target=&quot;_blank&quot;&gt;MRTK2&lt;/a&gt; to build a &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;Mixed Reality&lt;/a&gt; app to run on Immersive Headsets - or at least an environment where you control the environment using controllers - you can typically &#39;teleport&#39; forward and backward by pushing the thumbstick forward and backward, and rotate the view by pushing it left and right. Your viewpoint rotates either 90° left or 90° right (and of course you can change the view by rotating your head, but that&#39;s not the point). But what if you want to rotate only 45°? Or 22.5°? &lt;/p&gt;&lt;p&gt;I have spent quite some time looking in the MRKT2 config profiles looking for the rotation angle. The reason I could not find it, is because it&#39;s not there. At least, not directly. It&#39;s in the &lt;em&gt;pointer&lt;/em&gt; - the 3D cursor, if you like. Yeah, you read that right.&lt;/p&gt;&lt;h2&gt;First, we clone&lt;/h2&gt;&lt;p&gt;Starting from a default MRKT2 project, we follow the following steps:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Clone the Toolkit Configuration Profile itself. I used DefaultMixedRealityToolkitConfigurationProfile to start this time.&lt;/li&gt;&lt;li&gt;Under Input, clone the Mixed Reality Input System Profile&lt;/li&gt;&lt;li&gt;Under Pointers, clone the Mixed Reality Input Pointer Profile&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Finding the right pointer&lt;/h2&gt;&lt;p&gt;If you expand the Pointers section and then the Pointer Options sections, you will see a whole lot of pointers. The second one is the one we are looking for: ParabolicPointer. It&#39;s a prefab. &lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;439&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200603/01_pointeroptionsx.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;If you click it, it will open the asset folder Assets/MRTK/SDK/Features/UX/Prefabs/Pointers and highlight the prefab we just selected:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200622/02_assetsfolder.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;Finding the angle &lt;/h2&gt;&lt;p&gt;Press CTRL+D to duplicate the pointer, then drag it the Prefabs folder inside your app:&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200622/03_duplicateandmove.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;If that folder is not there yet, create it first. Once the prefab is inside the folder, rename it to something recognizable - ParabolicPointer45 for instance. When you are done renaming you have automatically selected the pointer itself. Go over to the inspector, scroll &lt;em&gt;all the way dow&lt;/em&gt;n to the &quot;Parabolic Teleport Pointer&quot; script and there, in the &quot;Teleport Pointer Settings&quot; section, you will &lt;em&gt;finally&lt;/em&gt; see where that bloody 90° comes from:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;223&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200622/04_pointersettings.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;Adapting and applying&lt;/h2&gt;&lt;p&gt;So now it&#39;s simply a matter of changing the angle to what you want, so for instance 45°. Then you go back to your adapted Mixed Reality Input Pointer Profile and drag your customized point in the right place:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;103&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200622/05_applypointer.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;and now your view will only rotate 45° in stead of 90° when you push the thumb stick sideways.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;I certainly did not expect this setting being contained in a &lt;em&gt;pointer,&lt;/em&gt; as by now I am quite conditioned to look in configuration profiles by now. I was - incorrectly - assuming it would be somewhere in the camera or controller settings. It took me quite some searching and debug breakpoints to find out where things where happening, and I took a few blind alleys before I found the right one. Typically something I blog about so a) I can now easily find back how I did it and b) you now also know where to look for. &lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://https://github.com/LocalJoost/RotationAngleDemo&quot; target=&quot;_blank&quot;&gt;Demo project, although it does not do very much, here.&lt;/a&gt; When you run it (either from Unity or as an app) in your Immersive Headset, you will see a green &#39;floor&#39; rotating only 45° when you push the thumbstick stick left or right. &lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2020/06/migrating-to-mrtk2-changing-viewpoint.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-8767877218946149888</guid><pubDate>Wed, 03 Jun 2020 09:28:00 +0000</pubDate><atom:updated>2020-06-03T11:55:51.590+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity3D</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Fixing the &quot;Windows Mixed Reality specific camera code has been moved...&quot; message when upgrading the MRKT2</title><description>&lt;p&gt;A very short and very simple trick today:&lt;/p&gt;&lt;p&gt;If you - like me - &lt;strike&gt;are crazy enough to&lt;/strike&gt; dutifully update the MRTK2 in your apps whenever a new version arrives, at some point you will notice a kind of puzzling, rather ominous and not very helpful message pop up in your console a couple of times:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&quot;Windows Mixed Reality specific camera code has been moved into Windows Mixed Reality Camera Settings. Please ensure you have this added under your Camera System&#39;s Settings Providers, as this deprecated code path may be removed in a future update.&quot;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;You might have seen this appear earlier, when you upgraded to MRTK 2.3, as you can see &lt;a href=&quot;https://github.com/microsoft/MixedRealityToolkit-Unity/commit/2ee39b763420554ccdadefaedf8e395516d43922#diff-82f03c0e8f3d624ac5a53c3a73e6c693&quot; target=&quot;_blank&quot;&gt;in this commit message&lt;/a&gt;. Version 2.3 was where it was introduced, in November 2019. The trouble with this message is that the procedure to fix it is not entirely clear - at least, it was not to me. Especially since the &lt;em&gt;first&lt;/em&gt; warning originates from the MRTK2 itself (when the MixedRealityCameraSystem is initialized as a service). Also GoogleBinging it yields no result, but I do hope it does so after I post this article. Fortunately it was also originating from my &lt;em&gt;own&lt;/em&gt; code, and that was a lot more helpful, as I know what &lt;em&gt;that&lt;/em&gt; is about - I wrote it myself, after all. &lt;/p&gt;&lt;p&gt;The culprit was this piece of code:&lt;/p&gt;&lt;pre&gt;CoreServices.CameraSystem.IsOpaque&lt;/pre&gt;&lt;p&gt;This ends up in Microsoft.MixedReality.Toolkit.CameraSystem.MixedRealityCameraSystem where the IsOpaque property is implemented. That looks for a &quot;IMixedRealityCameraSettingsProvider&quot;. That does not help much, but wat does help - in MixedRealityCameraSystem&amp;nbsp; there &lt;a href=&quot;https://microsoft.github.io/MixedRealityToolkit-Unity/Documentation/MixedRealityConfigurationGuide.html#camera&quot; target=&quot;_blank&quot;&gt;is a documentation link&lt;/a&gt; on top, which at least points you to the right &lt;em&gt;place&lt;/em&gt; in the configuration.&lt;/p&gt;&lt;p&gt;So what you need to do is very simple and takes 7 mouse clicks:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Select the MixedRealityToolkit node in your hierarchy&lt;/li&gt;&lt;li&gt;Select the Camera Tab&lt;/li&gt;&lt;li&gt;Expand the Camera Settings Provider&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;You will now see this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;354&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200603/01_mrcamerasystem.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Then:&lt;/p&gt;&lt;ol start=&quot;4&quot;&gt;&lt;li&gt;Click the &quot;+ Add Camera Settings Provider&quot; button just above &quot;Display Settings&quot;&lt;/li&gt;&lt;li&gt;Expand the &quot;New data provider 0&quot; section&quot;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200603/02_newdataprovider.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;ol start=&quot;6&quot;&gt;&lt;li&gt;For &quot;Type&quot;, select Microsoft.MixedReality.Toolkit.WindowsMixedReality/WindowsMixedRealityCameraSettings (this is actually two mouse clicks)&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;263&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200603/03_selectcamerasettings.png&quot; border=&quot;0&quot;&gt;&lt;br&gt;&lt;br&gt; The end result should look like this:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200603/04_done.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;.... and you are done. Unless you need to tweak the settings of the default camera settings profile, in which case you have to do the clone-and-adapt dance. But in any case, no more scary messages about deprecated code that might change in some future release. ;)&lt;/p&gt;&lt;p&gt;I omitted the customary demo project this time, as the point of this post was to show you how to get from A to B, not the end result.&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2020/06/fixing-windows-mixed-reality-specific.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-6010350203616313631</guid><pubDate>Fri, 22 May 2020 04:50:00 +0000</pubDate><atom:updated>2020-07-22T12:16:22.078+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity3D</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRTK2: using a Spatial Mesh inside the Unity Editor</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;&lt;img width=&quot;257&quot; height=&quot;146&quot; align=&quot;right&quot; style=&quot;border: 0px currentcolor; border-image: none; float: right; display: inline; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/00_hero.png&quot; border=&quot;0&quot;&gt;If you are developing an app using the &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;&lt;a href=&quot;https://github.com/Microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit&lt;/a&gt; 2 that requires interaction with a Spatial Mesh, the development process can become cumbersome: add code or assets with Unity and Visual Studio, create IL2CPP solution, wait, compile and deploy, wait, check behavior - rinse and repeat. You quickly learn to do as much as possible inside the Unity editor and/or use Holographic Remoting if you want stay productive and make your deadline. But a Spatial Mesh inside the Unity Editor does not exist.&lt;/p&gt;&lt;p&gt;... or does it? ;)&lt;/p&gt;&lt;h2&gt;Begun the Clone Wars have again&lt;/h2&gt;&lt;p&gt;You guessed it - before we can see anything at all, a lot of cloning and configuring of profiles needs to be happening first. &lt;/p&gt;&lt;ul&gt;&lt;li&gt;Clone the Toolkit Configuration Profile itself. I used DefaultHoloLens2CameraProfile this time. &lt;/li&gt;&lt;li&gt;Turn off the diagnostics (as ever).&lt;/li&gt;&lt;li&gt;Enable Spatial Awareness System&lt;/li&gt;&lt;li&gt;Clone the MixedRealityAwareness profile&lt;/li&gt;&lt;li&gt;Clone the MixedRealityAwarenessMeshObserver profile (the names of these things become more tongue-breaking the deeper you go)&lt;/li&gt;&lt;li&gt;Change the Display option (all the way down) to &quot;Occlusion&quot;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/02_occlusion.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;And now the interesting part&lt;/h2&gt;&lt;p&gt;On top of the Spatial Awareness System Settings there&#39;s this giant button over the whole length of the UI which is labeled &quot;+ Add Spatial Observer&quot;. &lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/03_addobserver.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;If you click that one, it will add a &quot;New data provider 1&quot; at the bottom, below the Display settings we just changed the previous step.&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/04_newdataprovider.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Select &quot;SpatialObjectMeshObserver&quot; for type&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;127&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/05_selectobserver.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And if you hit the play button, lo and behold:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;701&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/06_spatialmap1.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Basically you are now already where you want to be, but although the wireframe material works very well inside a &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt;, it does not work very well in an editor. At least, that is my opinion. &lt;/p&gt;&lt;h2&gt;Making the mesh more usable inside the editor&lt;/h2&gt;&lt;p&gt;You might have noticed the SpatialObjectMeshObserver comes with a with a profile &quot;DefaultObjectMeshObserverProfile&quot; - I&#39;d almost say &lt;em&gt;of course&lt;/em&gt; it does. Anyway, clone that one as well. Then we create a simple material:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;453&quot; height=&quot;480&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/07_material.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Of course using the &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;Mixed Reality&lt;/a&gt; Toolkit Standard shader. I only change the color to RGB 115,115,115 which is a kind of battleship grey. You make take any color you fancy, as far as I am concerned. Set that material to the &quot;Visible Material&quot; of the Spatial Mesh Object Observer you just added (&lt;em&gt;not&lt;/em&gt; in the material of the &quot;Windows Mixed Reality Spatial Mesh Observer&quot;!)&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;153&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/08_setmaterial.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;The result, if you run play mode again, is definitely better IMHO:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;694&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/09_spatialmap2.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;Using a mesh of a custom environment&lt;/h2&gt;&lt;p&gt;So it&#39;s nice to be able to use some sample mesh, but what if you need to the mesh of a real space? No worries, because just like in HoloLens1, the device portal allows you to download a scan of the current (real) space the HoloLens sees:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;391&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/10_downloadspatialmap.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;You can download this space by hitting the save button. This will download a SpatialMapping.obj file. Bring this into your Unity project, then drag it on top of the Spatial Mesh Object observer&#39;s &quot;Spatial Mesh Object&quot; property:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;151&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/11_usespatialmap.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And then, when you hit play mode, you will see the study where I have been hiding in during these worrying times. It has been my domain for working for the past 2.5 months for working and blogging, as well as following BUILD and the Mixed Reality Dev Days. If you download the &lt;a href=&quot;https://github.com/LocalJoost/ExampleSpatialMeshExample&quot; target=&quot;_blank&quot;&gt;demo project&lt;/a&gt;, it will also include a cube that moves forward, to show objects actually bounce off the fake spatial mesh, just like a real spatial mesh. &lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;697&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200520/12_bounce.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Note: if you compile and deploy this project to a HoloLens (either 1 or 2) you won&#39;t see this &#39;fake mesh&#39; at all. It only appears in the editor. Which is exactly what we want. It&#39;s for development purposes only.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Using this little technique you can develop for interacting with the Spatial Mesh while staying inside the Unity editor. You will need less access to a physical HoloLens 2 device, but more importantly speed up development this way. &lt;a href=&quot;https://github.com/LocalJoost/ExampleSpatialMeshExample&quot; target=&quot;_blank&quot;&gt;The demo project is, al always, on GitHub&lt;/a&gt;&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2020/05/migrating-to-mrtk2-using-spatial-mesh.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-3339402343746898270</guid><pubDate>Sun, 03 May 2020 16:21:00 +0000</pubDate><atom:updated>2020-05-05T18:56:47.702+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity3D</category><title>Migrating to MRTK2: right/left swappable hand menu&#39;s for HoloLens 2</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;As I announced in &lt;a href=&quot;https://twitter.com/LocalJoost/status/1230158501520998400&quot; target=&quot;_blank&quot;&gt;this tweet that you might remember from February&lt;/a&gt; the &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 2 version of my app &lt;a href=&quot;https://www.microsoft.com/store/productId/9P6SVQQCP2SQ&quot; target=&quot;_blank&quot;&gt;Walk the World&lt;/a&gt; sports two hand palm menus - a primary menu, often used command menu that is attached to your left hand that you operate with your right hand, and a secondary less-used settings menu that is attached to your right hand - and that you operate with your left. &lt;a href=&quot;https://twitter.com/_geniodelmale&quot; target=&quot;_blank&quot;&gt;Lorenzo Barbieri of Microsoft Italy, a.k.a. &#39;Evil Scientist&#39;&lt;/a&gt; ;) did the brilliant suggestion I should accommodate left-handed usage as well. And so I did - I added a button to the settings menu that actually swaps the &#39;handedness&#39; of the menus. This means: if you select &#39;left handed operation&#39; the &lt;em&gt;main&lt;/em&gt; menu is operated by your &lt;em&gt;left&lt;/em&gt; hand, and the secondary &lt;em&gt;settings&lt;/em&gt; menu by your &lt;em&gt;right&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;A little video makes this perhaps more clear:&lt;/p&gt;&lt;p&gt;&lt;iframe width=&quot;650&quot; height=&quot;365&quot; src=&quot;https://www.youtube.com/embed/5lm2z4KQTEk&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;&lt;p&gt;This blog explains how I made this work. I basically extracted the minimal code from my app and made it into a mini app that doesn&#39;t do more than make the menu swappable - both by pressing a toggle button and by a speech command. I will discuss the main points, but not everything in detail - but as always you can &lt;a href=&quot;https://github.com/LocalJoost/HandmenuHandedness&quot; target=&quot;_blank&quot;&gt;download a full sample project&lt;/a&gt; and see how it&#39;s working in the context of a complete running app.&lt;/p&gt;&lt;p&gt;This sample uses a few classes of my MRTKExtensions library of useful scripts.&lt;/p&gt;&lt;h2&gt;Configuring the Toolkit&lt;/h2&gt;&lt;p&gt;I won&#39;t cover this in much detail, but the following items need to be cloned and partially adapted:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The Toolkit Configuration Profile itself (I usually start with DefaultMixedRealityToolkitConfigurationProfile). Turn off the diagnostics (as ever)&lt;/li&gt;&lt;li&gt;The Input System Profile&lt;/li&gt;&lt;li&gt;The SpeechCommandsProfile&lt;/li&gt;&lt;li&gt;The RegisteredServiceProviderProfile&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Regarding the SpeechCommandsProfile: add two speech commands:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Set left hand control&lt;/li&gt;&lt;li&gt;Set right hand control&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;img width=&quot;585&quot; height=&quot;181&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200502/03_speechcommands.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;In the RegisteredServiceProviderProfile, register the Messenger Service that is in MRKTExtension.Messaging. If you have been following this blog, you will be familiar with this beast. I introduced this service as a Singleton behavior &lt;a href=&quot;https://dotnetbyexample.blogspot.com/2017/04/using-messenger-to-communicate-between.html&quot; target=&quot;_blank&quot;&gt;back in 2017&lt;/a&gt; and converted it to a service when the MRTK2 arrived.&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200502/02_services.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;&lt;img align=&quot;right&quot; style=&quot;border: 0px currentcolor; border-image: none; float: right; display: inline; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200502/04_hierarchy.png&quot; border=&quot;0&quot;&gt;Menu structure&lt;/h2&gt;&lt;p&gt;I already &lt;a href=&quot;https://dotnetbyexample.blogspot.com/2019/11/migrating-to-mrkt2-making-hand-palm-menu.html&quot; target=&quot;_blank&quot;&gt;explained how to make a hand menu&lt;/a&gt; last November,&amp;nbsp; and in &lt;a href=&quot;https://dotnetbyexample.blogspot.com/2020/05/migrating-to-mrkt2-easily-spacing-out.html&quot; target=&quot;_blank&quot;&gt;my previous blog post&lt;/a&gt; I explained how you should arrange objects that should be laid out in a grid (like buttons). The important things to remember are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;All objects that are part of a hand menu should be in a child object of the main menu object. In the sample project, this child object is called &quot;Visuals&quot; inside each menu.&lt;/li&gt;&lt;li&gt;All objects that should be easily arrangeable in a grid, should be in a separate child object within the UI itself. I always all this child object &quot;UI&quot;, and this is where you put a GridObjectCollection behaviour on.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Consistent naming makes a structure all the more recognizable I feel.&lt;/p&gt;&lt;h2&gt;Menu configuration&lt;/h2&gt;&lt;p&gt;The main palm menu has, of course, a Solver Handler and a Hand Constraint Palm Up behaviour. The tracked hand is set to the &lt;em&gt;left&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;251&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200502/05_palmmenu2.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;The tricky thing is always to remember - the main menu is going to be &lt;em&gt;operated&lt;/em&gt; by the dominant hand. For most people the dominant hand is &lt;em&gt;right&lt;/em&gt; - so the hand to be &lt;em&gt;tracked&lt;/em&gt; for the dominant menu is the &lt;em&gt;left&lt;/em&gt; hand&lt;em&gt;,&lt;/em&gt; because that leaves the &lt;em&gt;right&lt;/em&gt; hand free to actually operate controls on that menu. For &lt;em&gt;left&lt;/em&gt; hand control the main menu has to be set to track the &lt;em&gt;right&lt;/em&gt; hand. This keeps confusing me every time.&lt;/p&gt;&lt;p&gt;On the palm. It won&#39;t surprise you to see the Settings menu look like this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;299&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200502/06_settingsmenu2.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;With the Solver&#39;s TrackedHandedness set to Right. But here you also see the star of this little show: the DominantHandController, with the DominantHandController set to off - since I always have the settings menu operated by the non dominant hand, whatever that might be.&lt;/p&gt;&lt;h2&gt;DominantHandController&lt;/h2&gt;&lt;p&gt;This is actually a very simple script, that responds to messages sent from either a button or from speech commands:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;namespace MRTKExtensions.HandControl
{
    [RequireComponent(typeof(SolverHandler))]
    public class DominantHandHelper : MonoBehaviour
    {
        [SerializeField] 
        private bool _dominantHandControlled;
        private IMessengerService _messenger;
        private SolverHandler _solver;

        private void Start()
        {
            _messenger = MixedRealityToolkit.Instance.GetService&amp;lt;IMessengerService&amp;gt;();
            _messenger.AddListener&amp;lt;HandControlMessage&amp;gt;(ProcessHandControlMessage);
            _solver = GetComponent&amp;lt;SolverHandler&amp;gt;();
        }

        private void ProcessHandControlMessage(HandControlMessage msg)
        {
            var isChanged = SetSolverHandedness(msg.IsLeftHanded);
            if (msg.IsFromSpeechCommand &amp;amp;&amp;amp; isChanged &amp;amp;&amp;amp; _dominantHandControlled)
            {
                _messenger.Broadcast(new ConfirmSoundMessage());
            }
        }

        private bool SetSolverHandedness(bool isLeftHanded)
        {
            var desiredHandedness = isLeftHanded ^_dominantHandControlled ?&lt;br&gt;                                      Handedness.Left : Handedness.Right;
            var isChanged = desiredHandedness != _solver.TrackedHandness;
            _solver.TrackedHandness = desiredHandedness;
            return isChanged;
        }
    }
}
&lt;/pre&gt;&lt;p&gt;SetSolverHandedness determines what the handedness of the solver should be set to - depending on whether this menu is set to be controlled by the dominant hand or not, and whether or not left handed control is wanted. That&#39;s an XOR yes, you don&#39;t see that very often. But write out a truth table for those two parameters and that&#39;s where you will end up with. This little bit of code is what actually does the swapping of the menus from right to left and vice versa. &lt;/p&gt;&lt;p&gt;It also returns a value to see if the value has actually changed. This is because if the command is started from a &lt;em&gt;speech command&lt;/em&gt; we want, like any good &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;Mixed Reality&lt;/a&gt; developer, give some kind of audible cue the command has been understood and processed. After all, we can say a speech command any time we want, and if the user does not have a palm up, he or she won&#39;t see hand menu flipping from one hand to the other. So only if the command comes from a speech command, &lt;em&gt;and&lt;/em&gt; actual change has occurred, we need to give some kind of audible confirmation. I also added this confirmation only to be given by the &lt;em&gt;dominant&lt;/em&gt; hand controller - otherwise we get a double confirmation sound. After all, there are &lt;em&gt;two&lt;/em&gt; of these behaviours active - one for each menu.&lt;/p&gt;&lt;h2&gt;Supporting act: SettingsMenuController&lt;/h2&gt;&lt;p&gt;Of course, something still needs to respond to the Toggle Button being pressed. This is done by the this little behaviour:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;    public class SettingsMenuController : MonoBehaviour
    {
        private IMessengerService _messenger;

        [SerializeField]
        private Interactable _leftHandedButton;

        public void Start()
        {
            _messenger = MixedRealityToolkit.Instance.GetService&amp;lt;IMessengerService&amp;gt;();
            gameObject.SetActive(CapabilitiesCheck.IsArticulatedHandSupported);
            _messenger.AddListener&amp;lt;HandControlMessage&amp;gt;(ProcessHandControlMessage);
        }

        private void ProcessHandControlMessage(HandControlMessage msg)
        {
            if (msg.IsFromSpeechCommand)
            {
                _leftHandedButton.IsToggled = msg.IsLeftHanded;
            }
        }

        public void SetMainDominantHandControl()
        {
            SetMainDominantHandDelayed();
        }

        private async Task SetMainDominantHandDelayed()
        {
            await Task.Delay(100);
            _messenger.Broadcast(new HandControlMessage(_leftHandedButton.IsToggled));
        }
    }
}&lt;/pre&gt;&lt;p&gt;The SetMainDominantHandControl is called from the OnClick event in the Interactable behaviour on the toggle button:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200502/07_event.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;and then simply fires off the message based upon the toggle status of the button. Note that there&#39;s a slight delay, this has two reasons:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Make sure the sound the button plays actually has time to play&lt;/li&gt;&lt;li&gt;Make sure the button&#39;s IsToggled is actually set to the right value before we fire off the message. &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Yeah, I know, it&#39;s dicey but that&#39;s how it apparently needs to work. Also note this little script not only fires off HandControlMessage but also &lt;em&gt;listens&lt;/em&gt; to it. After all, if someone changes the handedness by speech commands, we want to see the button&#39;s toggle status reflect the actual status change.&lt;/p&gt;&lt;h2&gt;Some bits and pieces&lt;/h2&gt;&lt;p&gt;The final piece of code - that I only mention for the sake of completeness - is SpeechCommandProcessor :&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;namespace HandmenuHandedness
{
    public class SpeechCommandProcessor : MonoBehaviour
    {
        private IMessengerService _messenger;

        private void Start()
        {
            _messenger = MixedRealityToolkit.Instance.GetService&amp;lt;IMessengerService&amp;gt;();
        }

        public void SetLeftHanded(bool isLeftHanded)
        {
            _messenger.Broadcast(new HandControlMessage(isLeftHanded) &lt;br&gt;                                                        { IsFromSpeechCommand = true });
        }
    }
}&lt;/pre&gt;&lt;p&gt;It sits together with a SpeechInputHandler in Managers:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;141&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200502/08_speech.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Just don&#39;t forget to turn &lt;em&gt;off&lt;/em&gt; the &quot;Is Focus Required&quot; checkbox as these are global speech commands. Itned to forget this, and that makes for an amusing few minutes of shouting to your HoloLens without it having any effect, before the penny drops.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;You might have noticed I don&#39;t let the menu&#39;s appear &lt;em&gt;on&lt;/em&gt; your hand anymore but &lt;em&gt;next to &lt;/em&gt;your hand. This comes from the design guidelines on hand menu&#39;s on the official MRKT2 documentation, and although I can have a pretty strong opinion about things, I do tend to take some advice occasionally ;) - especially when it&#39;s about usability and ergonomics. Which is exactly why I made this left-to-right swappability in the first place. I hope this little blog post will give people some tools to add a little bit to inclusivity for HoloLens 2 applications.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/LocalJoost/HandmenuHandedness&quot; target=&quot;_blank&quot;&gt;Full project, as mostly always, here on GitHub.&lt;/a&gt;&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2020/05/migrating-to-mrtk2-rightleft-swappable.html</link><author>noreply@blogger.com (Joost van Schaik)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/5lm2z4KQTEk/default.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-1149317722530593914</guid><pubDate>Sat, 02 May 2020 15:16:00 +0000</pubDate><atom:updated>2020-05-03T18:54:24.023+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity3D</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRKT2 - easily spacing out menu buttons using GridObjectCollection</title><description>&lt;p&gt;&lt;img width=&quot;200&quot; height=&quot;199&quot; align=&quot;right&quot; style=&quot;border: 0px currentcolor; border-image: none; float: right; display: inline; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200501/01_menu.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;This is simple, short but I have to blog it because I discovered this, forgot about it, then discovered it again. So if anything this blog is for informing you as well al to make sure I keep remembering this &lt;em&gt;myself&lt;/em&gt;. &lt;/p&gt;&lt;p&gt;If you have done any UI design for &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;Mixed Reality&lt;/a&gt; or &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt;, you have been in this situation. The initial customer requirement ask for a simple 4 button menu. So you make a neat menu in a 2x2 grid, and are very satisfied with yourself. The next day suddenly you find out you need two more buttons. So - do you make a 2x3 or a 3x2 menu? You decide on the latter, painstakingly arrange them in a nice grid again.&lt;/p&gt;&lt;p&gt;The day after that, there&#39;s 2 more buttons. The day after that, 3 more. And the next day... you discover GridObjectCollection. Or in my case, &lt;em&gt;rediscover&lt;/em&gt; it.&lt;/p&gt;&lt;h2&gt;Simple automatic spacing&lt;/h2&gt;&lt;p&gt;So here is our simple 2x2 menu in the hierarchy. This is a hand menu. It has has a more complex structure than you would imagine, but this is because I am lazy and want an easily adaptable menu that can be organized by GribObjectCollection&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;309&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200501/02_structure.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;The point is, everything that needs to be easily organizable by GridObjectCollection, needs to be a child of the object that has the actual GridObjectCollection behaviour attached. In my case that&#39;s the empty gameobject &quot;Controls&quot;. Now Suppose I want this menu not to be 2x2 but 1 x 4. I simply need to change &quot;Num Rows&quot; into 1, press the &quot;Update Collection&quot; button and presto:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200501/03_menu2.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Of course, you will need to update the background plate and move the header text, but that&#39;s a lot less work than changing the layout of these buttons. Another example: change the default setting for &quot;Layout&quot; from &quot;Row Then Column&quot; to &quot;Colum then Row&quot;, set &quot;Num Rows&quot; to 1 again (for it will flip to 4 when you change the Layout dropdown) and press &quot;Update Collection&quot; again:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200501/04_menu4.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;You can also change how the button spacing by changing Cell Height and Cell Width. For instance, if I have a 4x4 grid and a cell distance width of 0.032 they are perfectly aligned together without any space in between (not recommended for real live scenario&#39;s where you are supposed to press these buttons - a mistake is easily made this way)&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200501/05_menu5.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;You can also do fun things like having then sorted out by name, child order, and both reversed. Or have them spaced out on not only a flat surface, but on a Cylinder, Sphere or a Radial area. &lt;/p&gt;&lt;p&gt;Note: the UpdateCollection can also be accessed by code, so you can actually use this script runtime as well. I mainly use it for static layouts.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Don&#39;t waste time in manual spacing, use this very handy tool in the editor to make a nice an evenly spaced button menu - or for whatever you need to have laid out. &lt;/p&gt;&lt;p&gt;Note:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Make it yourself easy by putting any parts of an UI that should be in a grid in a &lt;em&gt;separate empty game object&lt;/em&gt; and put the GridObjectCollection control on that, and place the other parts outside that, so they won&#39;t interfere with the layout process.&lt;/li&gt;&lt;li&gt;You can use this behaviour with any type of game object, not only buttons of course&lt;/li&gt;&lt;li&gt;More details about GridObjectCollection can be found on &lt;a href=&quot;https://microsoft.github.io/MixedRealityToolkit-Unity/Documentation/README_ObjectCollection.html&quot; target=&quot;_blank&quot;&gt;the documentation page on GitHub&lt;/a&gt;. This also handles related behaviours like ScatterObjectCollection and TileObjectCollection.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;No code so no project, although &lt;a href=&quot;https://dotnetbyexample.blogspot.com/2020/05/migrating-to-mrtk2-rightleft-swappable.html&quot; target=&quot;_blank&quot;&gt;in the next blog post&lt;/a&gt; this technique will be applied &#39;in real life&#39;, so to speak. &lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2020/05/migrating-to-mrkt2-easily-spacing-out.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-5433739822650921996</guid><pubDate>Fri, 24 Apr 2020 11:45:00 +0000</pubDate><atom:updated>2020-04-24T16:50:48.627+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity3D</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRTK2 - configuring, understanding and using Windows Mixed Reality controllers</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;&lt;img align=&quot;right&quot; style=&quot;margin: 0px 0px 0px 14px; border: 0px currentcolor; border-image: none; float: right; display: inline; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/controllers.jpg&quot; border=&quot;0&quot;&gt;Although the focus for &lt;a href=&quot;https://github.com/Microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit&lt;/a&gt; 2 now understandably is on Microsoft&#39;s big &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;Mixed Reality&lt;/a&gt; business player - &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 2 - it&#39;s still perfectly doable - and viable, IMHO - to develop Windows &lt;a href=&quot;https://www.microsoft.com/en-us/store/collections/MR-All-ImmersiveContent&quot; target=&quot;_blank&quot;&gt;Mixed Reality apps&lt;/a&gt; for WMR immersive headsets. Case in point: most of the downloads I get for my three Mixed Reality apps in the store come from people using immersive headsets - which is actually not that strange as immersive headsets are readily available for individuals whereas HoloLens (either 1 or 2) is not - and they cost 10-15% of an actual HoloLens to boot.&lt;/p&gt;&lt;p&gt;And the fun thing is, if you do this correctly, you can even make apps that run on both - with only minor device specific code. Using MRTK2, though, there are some minor problems to overcome:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The standard MRTK2 configuration allows for only limited use of all the controller&#39;s options&lt;/li&gt;&lt;li&gt;There are no samples - or at least none I could find - that easily shows how actually extend the configurations to leverage the controller&#39;s full potential&lt;/li&gt;&lt;li&gt;Ditto for samples on how to intercept the events and use those from code.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I intend to fix all of the above in this article. Once and for all ;)&lt;/p&gt;&lt;h2&gt;Configuration&lt;/h2&gt;&lt;p&gt;If you have worked a bit with the MRTK2 before, you know what&#39;s going to follow: cloning profiles, cloning profiles and more cloning profiles. We are going some four levels deep. Don&#39;t shoot the messenger ;)&lt;/p&gt;&lt;p&gt;Assuming you start with a blank Unity app with the MRTK2 imported, first step is of course to clone the Default profile - or whatever profile you wish to start with, by clicking Copy &amp;amp; customize.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;196&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/1_copydefaultprofile.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;While you are at it, turn off the diagnostics &lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;164&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/1a_diagnostics.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Next step is to clone the Input System Profile. You might need to drag the inspector a bit wider or you won&#39;t see the Clone button&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;251&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/2_cloneinput.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Step 3 is to clone the Controller Mapping Profile:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;539&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/3_clonecontrollers.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Expand the &quot;Controller Definitions&quot; section. If you then select Windows Mixed Reality Left Hand Controller, you will notice a lot of events are filled in for the various controls - but also that a couple are not:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;331&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/4_controllernotselectable.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;You can select something, but it&#39;s not applicable or already assigned to something else. The missing events are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Touchpad Press&lt;/li&gt;&lt;li&gt;Touchpad Position&lt;/li&gt;&lt;li&gt;Touchpad Touch&lt;/li&gt;&lt;li&gt;Trigger Touch&lt;/li&gt;&lt;li&gt;Thumbstick Press&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So we have to &lt;em&gt;add&lt;/em&gt; these events. To achieve this, we have to do one final clone: the Default Input Actions Profile.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;150&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/5_cloneinputactions.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And then you simply can add the five missing events (or input actions, as they are called in MRKT2 lingo). &lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;186&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/6_addinputactions.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Mind to select &quot;Digital&quot; for all new actions &lt;em&gt;except for Touchpad position&lt;/em&gt;. Make that a &quot;Dual Axis&quot;. That last one will be explained later.&lt;/p&gt;&lt;p&gt;Now you can once again go back to Input/Controller/Input Mappings settings, and, assign the proper (new) events to the controller buttons. Don&#39;t forget to do this for both the right and the left hand controller.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;333&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/7_assignactions.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And now &lt;em&gt;finally &lt;/em&gt;there are events attached to all the buttons to the controllers. Now it&#39;s time to show how to trap them.&lt;/p&gt;&lt;h2&gt;Understanding and using the events&lt;/h2&gt;&lt;p&gt;The important thing to understand is that there are different &lt;em&gt;kind&lt;/em&gt; of events, that all need to be trapped in a specific way. When I showed you to add the event types, all but one of them were &lt;em&gt;digital&lt;/em&gt; types. Only one was &quot;&lt;em&gt;Dual Axis&lt;/em&gt;&quot;. There actually are a lot of different types of events:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;256&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/8_inputactiontypes.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;I am not sure if I got all the details right, but this is what I found out:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;a &lt;em&gt;digital&lt;/em&gt; event, that&#39;s basically a click. You need to have a behaviour that implements IMixedRealityInputHandler to intercept this. Example: a click on the menu button &lt;/li&gt;&lt;li&gt;a &lt;em&gt;single axis&lt;/em&gt; event is an event that gives you a single value. The only application for WMR controllers I have found is a way to determine how far the trigger is pushed inwards (on a scale of 0-1). You will need to implement IMixedRealityInputHandler&amp;lt;float&amp;gt;&lt;/li&gt;&lt;li&gt;a &lt;em&gt;dual&lt;/em&gt; &lt;em&gt;axis&lt;/em&gt; event gives you two values. The only application I found was the touchpad - it gives to the X,Y coordinates where the touchpad was &lt;em&gt;touched. &lt;/em&gt;Range for both is -1 to 1. 0,0 is the touchpad&#39;s center. You will need to implement IMixedRealityInputHandler&amp;lt;Vector2&amp;gt;&lt;/li&gt;&lt;li&gt;a &lt;em&gt;six dof &lt;/em&gt;(degrees of freedom) event will give you a MixedRealityPose. This enables you to determine the current grip and pointer pose of the controller. &lt;em&gt; &lt;/em&gt;You will need to implement IMixedRealityInputHandler&amp;lt;MixedRealityPose&amp;gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Demo application&lt;/h2&gt;&lt;p&gt;I created an &lt;a href=&quot;https://github.com/LocalJoost/MRKT2WMRController&quot; target=&quot;_blank&quot;&gt;application that demonstrates&lt;/a&gt; the events you will get and what type they are. If available, it will also display the values associated with the event. It looks like this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;428&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/9_demoitems.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Not very spectacular, I&#39;ll admit, but it does the trick. On the top row it displays the &lt;em&gt;type&lt;/em&gt; of event intercepted, the bottom two rows show actual events with - in four cases - information associated with the events. When activated: the red circle will turn green.&lt;/p&gt;&lt;h2&gt;Observations using the demo app&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;You will notice you&#39;ll get a constant stream of Grip Pose and Pointer Pose events - hence these two events and the MixedRealityPose type events indicator are always green&lt;/li&gt;&lt;li&gt;You will also get a constants stream of &quot;Teleport Direction&quot; events (of type Vector2) from the thumbstick even if you don&#39;t touch it. I have no idea why this is so. I had to filter those out, or else the fact Touchpad position is a Vector2 element got hidden in the noise.&lt;/li&gt;&lt;li&gt;Grip press is supposed to be a SingleAxis event, but only fires Digital events&lt;/li&gt;&lt;li&gt;If you touch the touchpad, it actually fires &lt;em&gt;two&lt;/em&gt; events simultaneously - the Digital Touchpad Touch and the Vector2 Touchpad position. &lt;/li&gt;&lt;li&gt;Consequently, if you &lt;em&gt;press&lt;/em&gt; the touchpad, you get three events - Touchpad touch, Touchpad Position and Touchpad Press.&lt;/li&gt;&lt;li&gt;The trigger button also is an interesting story as that fires three events as well. As soon start to press it ever so slightly, it fires the SingleAxis event &quot;Trigger&quot; that tells you how far the trigger is depressed. But at the lowest scale where &quot;Trigger&quot; registers, it will &lt;em&gt;also &lt;/em&gt;fire the Digital &quot;Trigger Touch&quot; event. However, you will usually get a lot more &quot;Trigger&quot; events as it&#39;s very hard to keep the trigger perfectly still while it&#39;s halfway depressed. &lt;/li&gt;&lt;li&gt;And finally, when you fully press it, the Digital &quot;Select&quot; event will be fired.&lt;/li&gt;&lt;li&gt;Menu and Thumbstick press are simple Digital events as you would expect.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Key things to learn from the demo app&lt;/h2&gt;&lt;h3&gt;Registering global handlers&lt;/h3&gt;&lt;p&gt;On top you will see the the ControllerInputHandler implementing being derived from BaseInputHandler and the four interfaces mentioned. &lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;public class ControllerInputHandler : BaseInputHandler, 
    IMixedRealityInputHandler, IMixedRealityInputHandler&amp;lt;Vector2&amp;gt;, 
    IMixedRealityInputHandler&amp;lt;float&amp;gt;,
    IMixedRealityInputHandler&amp;lt;MixedRealityPose&amp;gt;&lt;/pre&gt;&lt;p&gt;The important thing to realize is that this behaviour needs to handle &lt;em&gt;global&lt;/em&gt; events. This implicates two things, first, you will have to register global handlers&lt;/p&gt;&lt;pre style=&quot;font-size: 11px;&quot;&gt;protected override void RegisterHandlers()
{
    CoreServices.InputSystem?.RegisterHandler&amp;lt;IMixedRealityInputHandler&amp;gt;(this);
    CoreServices.InputSystem?.RegisterHandler&amp;lt;IMixedRealityInputHandler&amp;lt;Vector2&amp;gt;&amp;gt;(this);
    CoreServices.InputSystem?.RegisterHandler&amp;lt;IMixedRealityInputHandler&amp;lt;float&amp;gt;&amp;gt;(this);
    CoreServices.InputSystem?.RegisterHandler&amp;lt;IMixedRealityInputHandler&amp;lt;MixedRealityPose&amp;gt;&amp;gt;(this);
};&lt;/pre&gt;&lt;p&gt;(and unregister them of course in UnregisterHandlers)&lt;/p&gt;&lt;p&gt;but second, if you use this in Unity, uncheck the &quot;Is Focus Required&quot; checkbox&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;400&quot; height=&quot;100&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/10_uncheckfocus.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;This will ensure the global handlers being registered properly and being intercepted by this controller.&lt;/p&gt;&lt;h3&gt;Discriminating between events of same type&lt;/h3&gt;&lt;p&gt;It might not be immediately clear, but the only way &lt;em&gt;I&lt;/em&gt; have been able to determine what exact event I get is to check it&#39;s MixedRealityInputAction.Description property. In the code you will seen things like&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;var eventName = eventData.MixedRealityInputAction.Description.ToLower();
if (eventName == &quot;touchpad position&quot;)
&lt;/pre&gt;&lt;p&gt;In fact, you will see that the names of the event displayers in Scene hierachy are bascially the names of the events without spaces. I simply find them by name&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;175&quot; height=&quot;202&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200418/11_eventdisplayers.PNG&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;After I simply load them in a dictionary in Start by looking for children in the &quot;Events&quot; object &lt;pre style=&quot;font-size: 12px;&quot;&gt;foreach (var controller in _eventDisplayParent.GetComponentsInChildren&amp;lt;SingleShotController&amp;gt;())
{
    _eventDisplayers.Add(controller.gameObject.name.ToLower(), controller);
}&lt;/pre&gt;

I simply find the back by looking in that dictionary and activating the SingleShot Controller. This class is part of a prefab that &lt;a href=&quot;https://dotnetbyexample.blogspot.com/2019/09/migrating-to-mrtk2-setting-up-and.html&quot; target=&quot;_blank&quot;&gt;I used and explained in an earlier post&lt;/a&gt;.&lt;pre style=&quot;font-size: 12px;&quot;&gt;private void ShowEvent(string eventName)
{
    var controller = GetControllerForEvent(eventName);
    if (controller != null)
    {
        controller.ShowActivated();
    }
}
private SingleShotController GetControllerForEvent(string controllerEvent)
{
    return _eventDisplayers[controllerEvent.ToLower().Replace(&quot; &quot;, &quot;&quot;)];
}&lt;/pre&gt;&lt;p&gt;I must say I feel a bit awkward about having to use strings to determine events by name. I guess it&#39;s inevitable if you want to be able to support multiple platforms and be able to add and modify events without actually having to change code and introduce types. This flexibility is what the MRTK2 intends to support, but it still feels weird.&lt;/p&gt;&lt;h3&gt;Combining events&lt;/h3&gt;&lt;p&gt;In the Immersive headset version of &lt;a href=&quot;https://www.microsoft.com/store/productId/9P6SVQQCP2SQ&quot; target=&quot;_blank&quot;&gt;Walk the World&lt;/a&gt; you can zoom in or out by pressing on top of at the bottom at of the touch pad. But as we have seem, it&#39;s not even possible to detect where the user has pressed, only &lt;em&gt;that&lt;/em&gt; he has pressed. But we can detect where he last &lt;em&gt;touched, &lt;/em&gt;which most likely is at or very near where he last touched. How you can combine these the touch and press events to deserve and effect like I just described, is showed in the relevant pieces of the &lt;a href=&quot;https://github.com/LocalJoost/MRKT2WMRController&quot; target=&quot;_blank&quot;&gt;demo project&lt;/a&gt; code that copied below:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;Vector2 _lastpressPosition;

public void OnInputChanged(InputEventData&amp;lt;Vector2&amp;gt; eventData)
{
    var eventName = eventData.MixedRealityInputAction.Description.ToLower();
    if (eventName == &quot;touchpad position&quot;)
    {
        _lastpressPosition = eventData.InputData;
    }
}

public void OnInputDown(InputEventData eventData)
{
    var eventName = eventData.MixedRealityInputAction.Description.ToLower();
    if (eventName == &quot;touchpad press&quot;)
    {
        // Limit event capture to only when more or less the top or bottom 
        // of the touch pad is pressed
        if (_lastpressPosition.y &amp;lt; -0.7 || _lastpressPosition.y &amp;gt; 0.7)
        {
            ShowEvent(eventName);
        }
    }
}&lt;/pre&gt;&lt;p&gt;First, the touchpad position event keeps the last position into a member variable, then when the touchpad is pressed, we check where it last was touched. The event is only fired when the front 30% or back 30% was last touched before it was pressed. If you press the sides (or actually, touch the side before you press) nothing happens. &lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Interacting with the controller has changed quite a bit since ye olde days of the HoloToolkit, but it still is pretty much doable and usable if you follow the rules and patterns above. I still find it odd I have to determine what event is fired by checking it&#39;s description, but I may just be missing something. However, this method works for me, at least in my application.&lt;/p&gt;&lt;p&gt;Also, I am a bit puzzled by the almost-the-same-but-not-quite-so events around trigger and touchpad. No doubt some serious considerations have been made implementing it like this, but not having been around while that happens, it leaves confused about the why. &lt;/p&gt;&lt;p&gt;Finally, bear in mind you usually don&#39;t have to trap Select manually, and neither is the Thumbstick (&#39;Teleport Direction&#39;) usually very interesting as those events are handled by the environment by default - the only reason I showed they them here was to demonstrate you &lt;em&gt;could&lt;/em&gt; actually intercept them.&lt;/p&gt;&lt;p&gt;Demo project, as always, &lt;a href=&quot;https://github.com/LocalJoost/MRKT2WMRController&quot; target=&quot;_blank&quot;&gt;here on GitHub&lt;/a&gt;. &lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2020/04/migrating-to-mrtk2-configuring.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-5965477494637897031</guid><pubDate>Mon, 13 Apr 2020 10:31:00 +0000</pubDate><atom:updated>2020-04-15T08:49:25.014+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity3D</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRKT2 - multi-device behaviour switching and scaling</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;The MRTK2 allows for development for both &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 1, HoloLens 2 and Windows &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;Mixed Reality&lt;/a&gt; immersive headsets with nearly identical code. And a growing number of other platforms, but the focus is now understandably on HoloLens 2. Yet, if you want to make apps with a broad range, you might as well use the capabilities the toolkit offers to run one app on all platforms.&lt;/p&gt;&lt;h2&gt;Rule-of-thumb device observations&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;On HoloLens 2, you typically want interactive stuff to be close by and relatively small, so you can leverage the touch functionality&lt;/li&gt;&lt;li&gt;On HoloLens 1 interactive stuff needs to be further away since the only control option you basically have is the air tap. But because it&#39;s further away, it needs to be bigger&lt;/li&gt;&lt;li&gt;On Windows Mixed Reality immersive headsets you want it also further away but even bigger still, as I have observed things seems to appear smaller on an immersive headset compared to HoloLens, and lower resolution headsets makes things harder to see things like small print compared to HoloLenses.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Basically this boils down to scaling and distance. Scaling usually is pretty simple to fix, but distance behavior is a bit more difficult, especially since the MRTK2 contains so much awesome behaviours for keeping for instance a menu in view, but it does not support different behavior for different devices. &lt;/p&gt;&lt;p&gt;&lt;img width=&quot;150&quot; height=&quot;180&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200413/menu.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;I have come up with a rather unusual solution for this, and it works pretty well.&lt;/p&gt;&lt;h2&gt;Meet the twins&lt;/h2&gt;&lt;p&gt;I made two behaviours that work in tandem. The first one is pretty simple enough and is called EnvironmentScaler.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;224&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200413/environmentscaler.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;This simply scales the current game object to the value entered for the specific device type. Notice also there is a drop down that will enable you to select view how platform specific sizes will appear inside the &lt;em&gt;Unity Editor. &lt;/em&gt;&lt;/p&gt;&lt;p&gt;The second one is a bit more odd. You see, for determining the right distance, I would like to use the standard Solver and RadialView combo. Of course I could have written a behaviour that changes the RadialView values based upon the detected platform. But then it would only have worked for RadialView. So I took a more radical and generic approach&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200413/environmentswitcher.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;As you can see, there is one Solver but no less that three RadialViews on the menu. They all have slightly different values for things like distance and Max view degrees. An if you start Play mode:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200413/runtime.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;It simply destroys and removes the behaviors for the other platforms. Crude, but very effective. And no coding required. The only thing is - there is no way to distinguish those three RadialViews, so it&#39;s best to add them to your game object in the same order as they are listed in the EnvironmentSwitcher: for HoloLens 1, HoloLens 2 and WMR headsets.&lt;/p&gt;&lt;h2&gt;The nuts and bolts&lt;/h2&gt;&lt;p&gt;Both the switcher and the scaler have the same generic base class:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;public abstract class EnvironmentHelperBase&amp;lt;T&amp;gt; : MonoBehaviour
{
    [SerializeField]
    private EditorEnvironmentType _editorEnvironmentType = EditorEnvironmentType.Hololens2;

    protected T GetPlatformValue(T hl1Value, T hl2Value, T wmrHeadsetValue)
    {
#if !UNITY_EDITOR
        if (CoreServices.CameraSystem.IsOpaque)
        {
            return wmrHeadsetValue;
        }

        var capabilityChecker = CoreServices.InputSystem as IMixedRealityCapabilityCheck;

        return capabilityChecker.CheckCapability(MixedRealityCapability.ArticulatedHand) ?&lt;br&gt;                hl2Value : hl1Value;
#else
        return GetTestPlatformValue(hl1Value, hl2Value, wmrHeadsetValue);
#endif
    }

    private T GetTestPlatformValue(T hl1Value, T hl2Value, T wmrHeadsetValue)
    {
        switch (_editorEnvironmentType)
        {
            case EditorEnvironmentType.Hololens2: return hl2Value;
            case EditorEnvironmentType.Hololens1: return hl1Value;
            default: return wmrHeadsetValue;
        }
    }
}&lt;/pre&gt;&lt;p&gt;The GetPlatformValue method accepts three values - one for every platform supported - and returns the right one for the current platform based upon this simple rules:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;If the headset is opaque, it&#39;s a WMR headset&lt;/li&gt;&lt;li&gt;If it&#39;s &lt;em&gt;not&lt;/em&gt; opaque and it supports articulated hands, it&#39;s a HoloLens 2&lt;/li&gt;&lt;li&gt;Otherwise it&#39;s a HoloLens 1&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;And there&#39;s also the GetTestPlatformValue that returns a platform based upon what&#39;s selected in the _editorEnvironmentType field, that can be used for testing in the editor. I have noticed that the editor returns false for opaque and true for the articulated hand support, so by default the code acts like it&#39;s in running in a HoloLens 2. Hence my &#39;manual switch&#39; in editorEnvironmentType so you can see what happens for the various devices inside your editor. For runtime code, whatever you selected in editorEnvironmentType in either behaviour is of no consequence.&lt;/p&gt;&lt;h2&gt;EnvironmentScaler implementation&lt;/h2&gt;&lt;p&gt;This is the very simple, as all the heavy lifting has already been done in the base class:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;public class EnvironmentScaler : EnvironmentHelperBase&amp;lt;float&amp;gt;
{
    [SerializeField]
    private float _hl1Scale = 1.0f;

    [SerializeField]
    private float _hl2Scale = 0.7f;

    [SerializeField]
    private float _immersiveWmrScale = 1.8f;

    void Start()
    {
        gameObject.transform.localScale *= GetPlatformValue(_hl1Scale, _hl2Scale,&lt;br&gt;                                                            _immersiveWmrScale);
    }
}&lt;/pre&gt;&lt;p&gt;Simply scale the object to the value selected by GetPlatformValue. Easy as cake.&lt;/p&gt;&lt;h2&gt;EnvironmentSwitcher implementation&lt;/h2&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;public class EnvironmentSwitcher : EnvironmentHelperBase&lt;monobehaviour&gt;
{
    [SerializeField]
    private MonoBehaviour _hl1Behaviour;

    [SerializeField]
    private MonoBehaviour _hl2Behaviour;

    [SerializeField]
    private MonoBehaviour _immersiveWmrBehaviour;

    void Start()
    {
        var selectedBehaviour = GetPlatformValue(_hl1Behaviour, _hl2Behaviour, 
                                                        _immersiveWmrBehaviour);
        foreach (var behaviour in new[] {_hl1Behaviour, _hl2Behaviour,&lt;br&gt;                                         _immersiveWmrBehaviour})
        {
            if (behaviour != selectedBehaviour)
            {
                Destroy(behaviour);
            }
        }
    }
}&lt;/monobehaviour&gt;&lt;/pre&gt;&lt;p&gt;Very much like the previous one, but now the values are not floats (for scale) but actual behaviors. It find the behaviour for the current device, the destroy all others.&lt;p&gt;The fun thing is - in this I used this specifically for three identical behaviours (that is, they are all RadialView behaviours) - one for every device. But it&#39;s just as easily possible to use three completely different behaviours, one for each device, and have the &#39;wrong&#39; ones be rendered inoperative by this behaviour as well. This makes this approach very generically applicable.&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Multi device strategy does not have to be complex. With these two behaviours you can make your app appear more or less the same on different devices, and still adhere to the device&#39;s unique capabilities.&lt;p&gt;Complete project, as always, &lt;a href=&quot;https://github.com/LocalJoost/MultiPlatformBehaviorSwitch&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;. Enjoy</description><link>http://dotnetbyexample.blogspot.com/2020/04/migrating-to-mrkt2-multi-device.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-4581693574941730579</guid><pubDate>Wed, 01 Apr 2020 06:18:00 +0000</pubDate><atom:updated>2020-04-15T08:50:33.654+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity3D</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRTK2 - using the non-native keyboard in touch scenarios</title><description>&lt;h2&gt;
Prelude&lt;/h2&gt;
With apologies for the uncharacteristic hiatus in my blog - last month I had a HoloLens 2 available for development and test purposes, and utilizing that opportunity to the max had a bit more priority than actually blogging about it. Then the world got hit head-on by the Corona madness and I had other things on my mind. Now, in self-isolation, hoping to avoid the virus (as no doubt most of you are right now), I have finally started to crank out the backed up blog posts I had chalked up &#39;for later&#39; while I was converting apps for HoloLens 2.&lt;br /&gt;
&lt;h2&gt;
Intro&lt;/h2&gt;
In the &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/Microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit&lt;/a&gt; 2 you can use the beautiful system keyboard for text input and that works amazingly well - in &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 2. Since MRKT2 development prioritizes HL2 development, and for good reason, this is not surprising. But in Immersive Headsets it works not so very well, and in HoloLens 1 it has the same problem. &lt;br /&gt;
Now since MRTK2.3 there&#39;s a new keyboard available - although actually it&#39;s an &lt;em&gt;old&lt;/em&gt; keyboard - the Keyboard prefab, that used to reside in HoloToolkit\UX\Prefabs, has been renamed to the NonNativeKeyboard prefab and now sits in MixedRealityToolkit.SDK\Experimental. It has a few advantages over the native keyboard:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;It is a Unity object, not a native object, so you can control size, rotation and position just like any other Unity object&lt;/li&gt;
&lt;li&gt;It has basically the same API and usage as the old keyboard, which makes it attractive to use in existing applications.&lt;/li&gt;
&lt;li&gt;It has a built-in button for speech recognition&lt;/li&gt;
&lt;li&gt;It gives a consistent look &amp;amp; feel for your apps.&lt;/li&gt;
&lt;/ul&gt;
It also has a few quite distinct disadvantages:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;It does not support touch events for HoloLens 2&lt;/li&gt;
&lt;li&gt;It does not take into account the differences in apparent size in WMR headsets and HoloLenses&lt;/li&gt;
&lt;li&gt;It should act different in various environments, (like being close when in HL2, and further away and bigger in other cases) which it does not&lt;/li&gt;
&lt;/ul&gt;
Now this, my friends, can be mitigated with a pretty simple add-on behaviour that I created. It takes care of positioning, platform dependent scaling and distance - but above all, it adds touch to the nonnative keyboard in a very simple way. &lt;br /&gt;
&lt;h2&gt;
How it works&lt;/h2&gt;
The start is simple enough: just some settings for each platform:&lt;br /&gt;
&lt;pre style=&quot;font-size: 12px;&quot;&gt;public class KeyboardAdapter : MonoBehaviour
{
    [SerializeField]
    private float Hl1Distance = 1.0f;
    [SerializeField]
    private float Hl1Scale = 1.0f;
    
    [SerializeField]
    private float Hl2Distance = 0.3f;
    [SerializeField]
    private float Hl2Scale = 0.3f;

    [SerializeField]
    private float WmrHeadSetDistance = 0.6f;

    [SerializeField]
    private float WmrHeadSetScale = 0.6f;

    [SerializeField] 
    private AudioClip _clickSound;

    private AudioSource _clickSoundPlayer;
}&lt;/pre&gt;
Basically a couple of settings. For every platform support (HoloLens 1, HoloLens 2 and &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;Mixed Reality&lt;/a&gt; headsets there is a distance from the user it will appear, and an apparent scale it will have. Also, you can assign a click sound for when a key is hit.&lt;br /&gt;
Almost all the heavy lifting happens in Start:&lt;br /&gt;
&lt;pre style=&quot;font-size: 12px;&quot;&gt;private void Start()
{
    _clickSoundPlayer = gameObject.AddComponent&amp;lt;AudioSource&amp;gt;();
    _clickSoundPlayer.playOnAwake = false;
    _clickSoundPlayer.spatialize = true;
    _clickSoundPlayer.clip = _clickSound;
    var buttons = GetComponentsInChildren&amp;lt;Button&amp;gt;();
    foreach (var button in buttons)
    {
        var ni = button.gameObject.AddComponent&amp;lt;NearInteractionTouchableUnityUI&amp;gt;();
        ni.EventsToReceive = TouchableEventType.Pointer;
        button.onClick.AddListener(PlayClick);
    }
}
&lt;/pre&gt;
The first four lines simply add and initialize the sound that is played when you tap a button. The next ones do the real work: they find every Button object in the keyboard, add a NearInteractionTouchableUnityUI to it, set the events to receive to &quot;pointer&quot; and add an event listener to the Button - that basically only serves to play the sound&lt;br /&gt;
The keyboard is built of Unity UI components, and adding a NearInteractionTouchableUnityUI and setting the EventToReceive to Pointer is all the is necessary to make the button &#39;think&#39; it&#39;s clicked when it&#39;s actually touched. And if you have set the ClickSound to an audio file in the editor, it plays a sound now when you tap our touch it, too.&lt;br /&gt;
Then we have these two properties who return the right value depending on the platform the app is running on:&lt;br /&gt;
&lt;pre style=&quot;font-size: 12px;&quot;&gt;private float Scale =&amp;gt; GetPlatformValue(Hl1Scale, Hl2Scale, WmrHeadSetScale);
private float Distance =&amp;gt; GetPlatformValue(Hl1Distance, Hl2Distance, WmrHeadSetDistance);
&lt;/pre&gt;
Which is done by this little method:&lt;br /&gt;
&lt;pre style=&quot;font-size: 12px;&quot;&gt;private float GetPlatformValue(float hl1Value, float hl2Value, float wmrHeadsetValue)
{
    if (CoreServices.CameraSystem.IsOpaque)
    {
        return wmrHeadsetValue;
    }

    var capabilityChecker = CoreServices.InputSystem as IMixedRealityCapabilityCheck;

    return capabilityChecker.CheckCapability(MixedRealityCapability.ArticulatedHand) ? 
            hl2Value : hl1Value;
}
&lt;/pre&gt;
If the headset is opaque, then it&#39;s a Windows Mixed Reality headset (or at least not a HoloLens), and otherwise we determine based upon the capability of tracking hands whether it&#39;s a HoloLens 1 or 2.&lt;br /&gt;
This is used to determine to show the keyboard on the desired place and at the desired scale.&lt;br /&gt;
&lt;pre style=&quot;font-size: 12px;&quot;&gt;public void ShowKeyboard()
{
    NonNativeKeyboard.Instance.PresentKeyboard();
    NonNativeKeyboard.Instance.RepositionKeyboard(CameraCache.Main.transform.position + 
                                                  CameraCache.Main.transform.forward * 
                                                  Distance, 0f);
    NonNativeKeyboard.Instance.gameObject.transform.localScale *= Scale;
}
&lt;/pre&gt;
And that is really all. Some creative use of components already in the MRTK2. &lt;br /&gt;
&lt;h2&gt;
Usage&lt;/h2&gt;
Just drop a NonNativeKeyboard prefab from the MRTK2 in your scene, and drop this behavior on it. You then only have to take care of two things. First, set Min and Max scale both to 1, otherwise this will interfere with the way the keyboard is scaled by this behavior:&lt;br /&gt;
&lt;img border=&quot;0&quot; src=&quot;https://www.schaikweb.net/blog/20200401/nnkeyboardsetting2.png&quot; style=&quot;background-image: none; border: 0px currentcolor;&quot; /&gt;&lt;br /&gt;
And of course, you have to set some parameters for the behavior itself. I have chosen what I like to think are reasonable settings for every platform:&lt;br /&gt;
&lt;img border=&quot;0&quot; src=&quot;https://www.schaikweb.net/blog/20200401/adapterparams.png&quot; style=&quot;background-image: none; border: 0px currentcolor;&quot; /&gt;&lt;br /&gt;
And of course the sound that appears when you tap or touch the keyboard&lt;br /&gt;
&lt;h2&gt;
How it looks&lt;/h2&gt;
On a HoloLens 2( in my app &lt;a href=&quot;https://www.microsoft.com/store/productId/9P6SVQQCP2SQ&quot; target=&quot;_blank&quot;&gt;Walk the World&lt;/a&gt; for HoloLens 2) it looks like this:&lt;br /&gt;
&lt;img border=&quot;0&quot; height=&quot;273&quot; src=&quot;https://www.schaikweb.net/blog/20200401/hl2keyboard.png&quot; style=&quot;background-image: none; border: 0px currentcolor;&quot; width=&quot;640&quot; /&gt;&lt;br /&gt;
It is pretty close, as you are supposed to be able to touch it&lt;br /&gt;
On HoloLens 1 it looks like this:&lt;br /&gt;
&lt;img border=&quot;0&quot; height=&quot;360&quot; src=&quot;https://www.schaikweb.net/blog/20200401/hl1keyboard.jpg&quot; style=&quot;background-image: none; border: 0px currentcolor;&quot; width=&quot;640&quot; /&gt;&lt;br /&gt;
Pretty much the same - bigger, but further away and thus easier to control with the air tap. Since this is a 2D picture, the differences between HoloLens 1 and 2 are actually hard to spot. And on Mixed Reality it looks like this - it look smaller, but that&#39;s because in a headset everything looks smaller. It&#39;s apparent size is the same as in HoloLens 1.&lt;br /&gt;
&lt;img border=&quot;0&quot; height=&quot;360&quot; src=&quot;https://www.schaikweb.net/blog/20200401/MRkeyboard.jpg&quot; style=&quot;background-image: none; border: 0px currentcolor;&quot; width=&quot;640&quot; /&gt;&lt;br /&gt;
And finally, and action movie of touch enabled non native keyboard on a HoloLens 2. You can actually touch the buttons and they respond with a button press sound, as you see&lt;br /&gt;
&lt;iframe allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot; frameborder=&quot;0&quot; height=&quot;365&quot; src=&quot;https://www.youtube.com/embed/gbniG4wZT9U&quot; width=&quot;650&quot;&gt;&lt;/iframe&gt;&lt;br /&gt;
&lt;h2&gt;
Why no solvers?&lt;/h2&gt;
You might have noticed I did not use any MRTK2 solvers to keep the keyboard floating in view when you when you move your head, our keep a dynamical distance. I did initially, but I found out that a keyboard that actually moves is very annoying when you are trying to type, especially when using touch type. Then the keyboard is close, and a move is easily triggered when you want to do things like first type the A (left), then the P (totally right). So I decided just to let it appear right in front of the user, and keep it there. If you don&#39;t use it, it automatically disappears after a configurable timeout. This is built into the keyboard, that is not my doing. &lt;br /&gt;
&lt;h2&gt;
Conclusion&lt;/h2&gt;
It&#39;s quite remarkable how you can make &#39;old&#39; components interplay with the new HoloLens 2 interaction possibilities using the new components and a bit of imagination. Also, the MRTK2 made making apps that run on both HoloLens 1, HoloLens 2 &lt;em&gt;and&lt;/em&gt; Windows Mixed Reality headsets a lot easier. Although it&#39;s clear MRTK2 prioritizes HoloLens 2 above all - and that&#39;s logical, because there&#39;s where the action and the business is. I fear the venerable trailblazing HoloLens 1, will quickly lose mindshare and disappear from the radar when HoloLens 2 becomes more widely available. I am not quite sure what to do with mine, but time will tell.&lt;br /&gt;
As usual, you can find the code of the little project &lt;a href=&quot;https://github.com/LocalJoost/NonNativeKeyboardDemo&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;&lt;em&gt;&lt;/em&gt;. If you want to run the app and see the keyboard, just say &quot;Open Keyboard&quot;. &lt;br /&gt;
&lt;h2&gt;
Post scriptum&lt;/h2&gt;
I put the part that makes the NonNativeKeyboard touchable in a &lt;a href=&quot;https://github.com/microsoft/MixedRealityToolkit-Unity/pull/7611&quot; target=&quot;_blank&quot;&gt;pull request to the MRTK2&lt;/a&gt; that was merged at April 14, and will be part of the MRTK 2.4.0 release.</description><link>http://dotnetbyexample.blogspot.com/2020/04/migrating-to-mrtk2-using-non-native.html</link><author>noreply@blogger.com (Joost van Schaik)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/gbniG4wZT9U/default.jpg" height="72" width="72"/><thr:total>2</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-5918114244949603917</guid><pubDate>Wed, 12 Feb 2020 15:50:00 +0000</pubDate><atom:updated>2020-02-14T08:03:48.561+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><title>HoloLens 2 - it&#39;s the interaction, stupid!</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;&lt;img width=&quot;100&quot; height=&quot;100&quot; align=&quot;right&quot; style=&quot;border: 0px currentcolor; border-image: none; float: right; display: inline; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200212/HL2.jpg&quot; border=&quot;0&quot;&gt;Tuesday, February 7, 2020 marked an important occasion to me. A &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 2 arrived at my door. For the first time since March 2019, where I got to try a HoloLens 2 during the MVP Summit for a few minutes, I actually had my hands on a device. And what&#39;s more - I got a &lt;em&gt;month&lt;/em&gt; to play, test and develop with it, courtesy of fellow MVP and Regional Director &lt;a href=&quot;https://www.linkedin.com/in/philippbauknecht/&quot; target=&quot;_blank&quot;&gt;Philipp Bauknecht&lt;/a&gt; of &lt;a href=&quot;https://media-lesson.com/&quot; target=&quot;_blank&quot;&gt;MediaLesson GbmH&lt;/a&gt;, a real community hero, who has graciously provided me with this learning opportunity. I hope I will be someday be able to repay him this enormous favor.&lt;/p&gt;&lt;p&gt;Just having this device around and being able to test and develop with it, quite changed my views of it,&amp;nbsp; what&#39;s actually important - and what makes it a game changer.&lt;/p&gt;&lt;h2&gt;Display&lt;/h2&gt;&lt;p&gt;&lt;img width=&quot;200&quot; height=&quot;264&quot; align=&quot;left&quot; style=&quot;margin: 0px 8px 0px 0px; border: 0px currentcolor; border-image: none; float: left; display: inline; background-image: none;&quot; src=&quot;https://media1.britannica.com/eb-media/86/125686-050-8BCF5879.jpg&quot; border=&quot;0&quot;&gt;First of all, I am going to bring your hopes down a little. To an extent, HoloLens 2 suffers a bit from what I would like to coin as &quot;the Apollo 12 effect&quot;. The whole world followed Neil Armstrong, Buzz Aldrin and Michael Collins to the Moon and where glued to very bad black &amp;amp; white screen while the first two men took their steps to the Moon. But a lot &lt;a href=&quot;https://www.theatlantic.com/science/archive/2019/11/apollo-12-moon-landing-nasa/601939/&quot; target=&quot;_blank&quot;&gt;less people watched Apollo 12&lt;/a&gt;. Successive flights got even less attention (bar Apollo 13, but that was not because they landed, but almost died). People had literally seen this before and were - I kid you not - complaining about footage of men on the Moon eating up precious TV time from &lt;em&gt;the football games&lt;/em&gt;. Subsequent flights after 17 were cancelled. People are extremely well equipped at accepting &#39;magic&#39; and then getting bored with it.&lt;/p&gt;&lt;p&gt;As far as display goes, HoloLens 2 shows you virtual objects in 3D space that can interact with reality. This, my friends, is exactly what HoloLens 1 did. &lt;/p&gt;&lt;p&gt;It does this a lot faster, the view is brighter, the holograms are a lot more stable, and the thing almost everyone harped on - the field of view - has been considerably increased. I can almost imagine Alex Kipman shouting &quot;we gave you bloody magic and all you kept telling me was the view was not big enough - &lt;em&gt;are you happy now???&lt;/em&gt;&quot;&lt;/p&gt;&lt;p&gt;There are other things: the device is a lot more ergonomic, it feels lighter but actually isn&#39;t very much - it&#39;s just better balanced. Donning it as easy as cake, taking it off as well, and charging via USB-C is a godsend - no more fiddling with MicroUSB on a wobbly end. I have seen more than one HoloLens 1 with a damaged charging port.&lt;/p&gt;&lt;p&gt;That&#39;s all very fine and welcome. But that&#39;s not what I mean by game changing. If we stay in space terminology - HoloLens 1 was like we suddenly had a fusion rocket. It is awesome, but parts of it were messy.&lt;/p&gt;&lt;p&gt;HoloLens 2 has a warp drive.&lt;/p&gt;&lt;h2&gt;Interaction, interaction, interaction&lt;/h2&gt;&lt;p&gt;Everyone who has ever used HoloLens 1 - or better still, tried instructing a newbie user to use one - knows the challenge. You can select something by pointing your head to a Hologram, then perform an air tap. Just tap your finger and thumb together. Easy as cake. And yet I have witnessed people who for some reason could not perform this simple task successfully. Either they pointed the cursor not correctly, or they made gestures that were almost but not quite an air tap, made it to slow or too fast, contorted their hands in a way that apparently confused the device, or started to make up gestures - that of course did not work at all. Whatever. &lt;em&gt;Most&lt;/em&gt; people got it, but between 10-20% just never could get it to work reliably, if at all. The HoloLens 1 came with a little clicker for those people, apparently a last-minute addition - that almost no-one ever used. It either lost its charge at an inconvenient time, or (in most cases) got completely lost at all, it being a small device that easily was forgotten or dropped somewhere.&lt;/p&gt;&lt;p&gt;HoloLens 2 does not come with a clicker, and that&#39;s for a reason. If you make a gesture that even &lt;em&gt;remotely&lt;/em&gt; resembles an air tap, it registers it as such - with such ferocity and accuracy that if you have a large contact surface you might even get some inadvertent air taps in (I will have to look into that for my app &lt;a href=&quot;https://www.microsoft.com/store/productId/9P6SVQQCP2SQ&quot; target=&quot;_blank&quot;&gt;Walk the World&lt;/a&gt;, for instance).&amp;nbsp; &lt;/p&gt;&lt;p&gt;In addition, what everyone saw being demoed first by the amazing &lt;a href=&quot;http://twitter.com/julenka&quot; target=&quot;_blank&quot;&gt;Julia Schwarz&lt;/a&gt; - the ability just to touch, grab and move Holograms works &lt;em&gt;amazingly well&lt;/em&gt;. To such an extent that you can push buttons like they are real, grab, move and rotate things like they are real... everything with amazing accuracy. You can even have your hand visualized and then it looks like you have some computer-generated glove on your hands - it follows every little movement. The resulting interaction model is very natural. So natural that you actually at first expect haptic feedback when you push a &#39;button&#39;. Maybe something for HoloLens 3 ;).&lt;/p&gt;&lt;p&gt;There are a few things you might want to explain when you instruct someone to use the device for the first time to speed things up - like that if you want to use the start button, that&#39;s on your wrist. No more bloom gestures – the Italians will appreciate that ;). You might want to explain how an air tap works, but it&#39;s likely people find that out by themselves as it is so easy now. Also, the device goes out of it&#39;s way to explain itself on first startup.The fact that it can not only track your hand but also recognizes all kinds of hand postures and gestures allows for much more detailed control , and my personal favorite is having menus popup when you hold one hand in a certain position. These &lt;a href=&quot;http://dotnetbyexample.blogspot.com/2019/11/migrating-to-mrkt2-making-hand-palm-menu.html&quot; target=&quot;_blank&quot;&gt;hand palm menus&lt;/a&gt; can be very easily made, using no code at all, just using stuff that&#39;s included in the MRKT2 out of the box.&lt;/p&gt;&lt;h2&gt;But wait, there&#39;s more&lt;/h2&gt;&lt;p&gt;Voice commands, remember that? The thing that everyone used like crazy and then quickly came back from, as it did not always work in noisy environments, especially with a lot of talk around. And making an odd gesture in empty space and looking weird is one thing, but shouting repeatedly at a device makes you feel very awkward indeed. Whatever they did to it, it&#39;s now way more accurate and confident at recognizing speech. Even in a very loud room with people talking. Speech control is everywhere in HoloLens 2, and very easy to use reliably.&lt;/p&gt;&lt;p&gt;And then there&#39;s eye tracking. Remember you had to move your whole head to point the gaze cursor? It now tracks your &lt;em&gt;eyes&lt;/em&gt;. I knows what your are &lt;em&gt;looking&lt;/em&gt; at. I use this in &lt;a href=&quot;https://www.microsoft.com/store/productId/9P6SVQQCP2SQ&quot; target=&quot;_blank&quot;&gt;AMS HoloATC&lt;/a&gt; to make an image of the actual airplane pop up when you look at the model. There are four (or five, depending on what you include) events that &lt;a href=&quot;http://dotnetbyexample.blogspot.com/2019/09/migrating-to-mrtk2-setting-up-and.html&quot; target=&quot;_blank&quot;&gt;you can easily track&lt;/a&gt;. I also learned that on a real life device I make that happen way too fast and too nervously. Having a real device, I will be able to fix this in the near future. &lt;/p&gt;&lt;p&gt;Eye tracking also has some extra benefits - first of all, it allows the device to use Windows Hello login using &lt;em&gt;iris recognition&lt;/em&gt;. Second, calibrating is a lot easier and faster. No longer do you have first close your one eye, then very very precisely move your finger in the right slot for a couple of times, and repeat that for the other eye - you now simply have to track a few holograms with your eyes, as they move though your view. And you really should do that - Microsoft pushed the envelope a lot further when it comes to display technology, so if you don&#39;t calibrate properly, there&#39;s a lot more chance of having a fuzzy view. Fortunately the device has a setting that automatically starts the calibration routine when it detects the user has changed (which it presumably does using the iris scan).&lt;/p&gt;&lt;h2&gt;In conclusion&lt;/h2&gt;&lt;p&gt;HoloLens 2 is an amazing device, with an amazing display technology - but it&#39;s the interaction model that makes it really special. This is what takes in over the top, makes it natural, simple to learn and easy to use. The hand/eye tracking removes the barrier of artificial gestures and make wandering around and interacting with Holograms at lot easier. This will make use of the device in business settings - especially industrial and manufacturing environments - a lot easier.&lt;/p&gt;&lt;p&gt;I love living in the future:)&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2020/02/hololens-2-its-interaction-stupid.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>5</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-3291513647731727564</guid><pubDate>Wed, 22 Jan 2020 06:57:00 +0000</pubDate><atom:updated>2020-01-22T07:57:22.141+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">Microsoft Store</category><category domain="http://www.blogger.com/atom/ns#">Visual Studio 2019</category><title>Mixed Reality apps failing the WACK - revisited</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;Last September I wrote &lt;a href=&quot;http://dotnetbyexample.blogspot.com/2019/09/migrating-to-mrtk2submitting-hololens-2.html&quot; target=&quot;_blank&quot;&gt;about various hoops you had to jump through&lt;/a&gt; to get a &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;Mixed Reality&lt;/a&gt; app to appear in the store and not have it to fail&amp;nbsp; the Windows Application Certification Toolkit (WACK). It&#39;s time for a little update, since part of what I wrote is no longer necessary, and some things have changed subtly.&lt;/p&gt;&lt;h2&gt;The good news&lt;/h2&gt;&lt;p&gt;I wrote how you manually had to mess with Package.AppManifest.xml, particularly how you had to remove the DirectX 10 dependency to make it (still) downloadable for &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 1 devices. Provided you have been diligently installing the service updates that are issued for this device, by now your HoloLens 1 should have been updated to no longer reject apps that have this requirement. So no need to take this step anymore.&lt;/p&gt;&lt;h2&gt;The bad news&lt;/h2&gt;&lt;p&gt;The trick I wrote how to make sure some unsupported DLLs were not included does not work anymore. We have to resort to more drastic measures.&lt;/p&gt;&lt;h2&gt;The problem&lt;/h2&gt;&lt;p&gt;To recap: if you generate a C++ solution from the recommended Unity version (2018.4.x, LTS branch) you get the following errors in the WACK&lt;/p&gt;&lt;ul&gt;&lt;li&gt;HolographicAppRemoting.dll has failed the AppContainerCheck check.
&lt;li&gt;PerceptionDevice.dll has failed the AppContainerCheck check.&lt;li&gt;UnityRemotingWMR.dll has failed the AppContainerCheck check.&lt;/li&gt;&lt;li&gt;The Supported API test will list 10 errors concerning UnityRemotingWMR calling unsupported APIs
&lt;li&gt;The Debug configuration test will tell youUnity RemotingWMR is only built in debug mode
&lt;li&gt;And if you try to build for x86 or ARM, the Package sanity test will tell you HolographicAppRemoting.dll, PerceptionDevice.dll and UnityRemotingWMR.dll are only available for x64.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I suggested editing the “Unity Data.vcxitems” file that is inside your store projects and change the contents of the DeploymentContent-tags for these files to false. That worked swell. Until, somewhere between Unity and Visual Studio versions upgrade, some brilliant person thought it a good idea to recreate &lt;em&gt;that file whenever you change the architecture&lt;/em&gt;. So when you change the architecture from &quot;x86&quot; to &quot;ARM&quot; - &lt;em&gt;something that happens automatically while creating multi-platform packages&lt;/em&gt; - you are greeted with this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;417&quot; src=&quot;https://www.schaikweb.net/blog/20200120/refresh.png&quot;&gt;&lt;/p&gt;&lt;p&gt;M previous solution does not work anymore, as the three offending DLLs are automatically added &lt;em&gt;back&lt;/em&gt; to the file, and thus the WACK fails again. Some more brute force is being required.&lt;/p&gt;&lt;h2&gt;The solution&lt;/h2&gt;&lt;p&gt;If you expand the Il2CppOutputProject, you can simply &lt;em&gt;see&lt;/em&gt; the offending DLL&#39;s. The solution is as simple as selecting &lt;em&gt;and deleting them&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;200&quot; height=&quot;276&quot; src=&quot;https://www.schaikweb.net/blog/20200120/culprits.png&quot;&gt;&lt;/p&gt;&lt;p&gt;And then you can generate your packages, run the WACK again... and may get another failure:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;107&quot; src=&quot;https://www.schaikweb.net/blog/20200120/failagain.png&quot;&gt;&lt;/p&gt;&lt;p&gt;This depends on the version of the WACK you have. &lt;a href=&quot;https://social.msdn.microsoft.com/Forums/en-US/10712943-b0be-4b83-a36a-169debd2d7b1/known-issue-windows-app-certification-kit-reports-supported-apis-test-due-to-cxxframehandler4?forum=Win10SDKToolsIssues&quot; target=&quot;_blank&quot;&gt;As as you can read here, the Store has been updated to ignore this error&lt;/a&gt;. In other words, if you are down to this error, you are good to go for submission and (successful) certification in the &lt;a href=&quot;https://www.microsoft.com/en-us/store/apps/windows&quot; target=&quot;_blank&quot;&gt;Windows Store&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Mind you - as soon as you regenerate the solution from Unity, the project file may or may not be overwritten and you will need to repeat this. So - &lt;em&gt;always always&lt;/em&gt; run the WACK before you submit - but then again, that&#39;s what you should do anyway.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;The ever changing constellations of Unity and Visual Studio keep us throwing curveballs, but fortunately there&#39;s always been a way around. So far. Happy submitting!&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2020/01/mixed-reality-apps-failing-wack.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-813585514314390965</guid><pubDate>Wed, 15 Jan 2020 13:33:00 +0000</pubDate><atom:updated>2020-01-15T17:38:36.803+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">Microsoft Store</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Mixed Reality apps failing MS Store validation on &quot;10.5.1 Personal information&quot; - and how to fix it</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;After fellow MVP (and RD, although not fellow-) &lt;a href=&quot;http://twitter.com/grillphil&quot; target=&quot;_blank&quot;&gt;Philipp Bauknecht&lt;/a&gt; discovered a bug in my &lt;a href=&quot;https://www.microsoft.com/store/productId/9P6SVQQCP2SQ&quot; target=&quot;_blank&quot;&gt;AMS HoloATC&lt;/a&gt; app while testing it on his &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 2 (what lunatic runs HoloLenses with DE-DE settings anyway :P) I created a fix for that and submitted the fixed app to the Microsoft Store on Saturday January 12th. I assumed it would pass right though. It did not. Monday morning I got a failure message:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;177&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200115/fail.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;That was not what I hoped, and certainly not what I expected&lt;/p&gt;&lt;h2&gt;Parsing the message&lt;/h2&gt;&lt;p&gt;I will admit to being rather confused, because I &lt;em&gt;have&lt;/em&gt; a privacy policy link. In the Store. But apparently something has changed. If you want all the details, you can find them in the new &lt;a href=&quot;https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE4o4bH&quot; target=&quot;_blank&quot;&gt;App Developer Agreement&lt;/a&gt;, but the text in the failure basically says it all:&lt;/p&gt;&lt;p&gt;&quot;For in-product include the privacy policy URL under the settings section&quot;&lt;/p&gt;&lt;p&gt;So I need to add a privacy policy &lt;em&gt;in the app&lt;/em&gt;. Okay. But they are expecting it under the &lt;em&gt;settings section&lt;/em&gt;. This is clearly written with a&lt;em&gt; desktop app&lt;/em&gt; in mind. But I don&#39;t have a main screen, let alone I can put a hamburger menu somewhere and put a &quot;Settings&quot; entry in this.&lt;/p&gt;&lt;h2&gt;Solution part 1&lt;/h2&gt;&lt;p&gt;So I could clearly not obey the &lt;em&gt;letter&lt;/em&gt; of the law, but I can try to act in &lt;em&gt;spirit&lt;/em&gt;. So I ventured out to see if I could get a privacy policy in the app in a way that would be acceptable to the Store testers. All my apps have like a &quot;help&quot; function, that you can activate by saying &quot;help&quot; or &quot;show help&quot;. I actually advertise that on start-up with a floating text that is shortly visible after starting up the app.&lt;/p&gt;&lt;p&gt;This help screen looked like this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;323&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200115/helpscreen1.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And now looks like this&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;322&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200115/helpscreen2.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;I actually added a text and a quad behind it, just ahead of the quad that is the main backdrop. &lt;/p&gt;&lt;p&gt;&lt;img width=&quot;346&quot; height=&quot;216&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200115/helpscreen3.png&quot; border=&quot;0&quot;&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200115/helpboardlayout.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And on that quad there a very simple and crude behavior&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;using Microsoft.MixedReality.Toolkit.Input;
using UnityEngine;
using UnityEngine.WSA;

public class BrowserActivator : MonoBehaviour, IMixedRealityPointerHandler
{
    [SerializeField]
    private string _urlToOpen;

    public void OnPointerDown(MixedRealityPointerEventData eventData)
    {
        Launcher.LaunchUri(_urlToOpen,false);
    }

    public void OnPointerDragged(MixedRealityPointerEventData eventData)
    {
    }

    public void OnPointerUp(MixedRealityPointerEventData eventData)
    {
    }

    public void OnPointerClicked(MixedRealityPointerEventData eventData)
    {
    }
}
&lt;/pre&gt;&lt;p&gt;that launches a browser window when you tap on the text. In the app it looks like this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;360&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200115/helpscreenhl.jpg&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And if you tap the indicated text, the app will move to the background and you will see this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;360&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200115/pphl.jpg&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Or whatever URL you put into _urlToOpen in the editor. I took &lt;a href=&quot;http://schaikweb.net/AMSHoloATC/aa_en.html&quot; target=&quot;_blank&quot;&gt;the exact same URL&lt;/a&gt; as in the Store. I am pretty sure this won&#39;t win me any design awards - but that&#39;s not the goal here. Note: these images where created with a HoloLens 1, as I am still lacking a HoloLens 2 (if anyone from Microsoft reads this - yes, that&#39;s a hint ;) ).&lt;/p&gt;&lt;h2&gt;Solution part 2&lt;/h2&gt;&lt;p&gt;Since I have no settings section, I thought it prudent to tell the testers where they could find the link to the privacy policy. So under &quot;Submission options&quot; I explained the why, where and how like this:&lt;/p&gt;&lt;p&gt;&quot;&lt;strong&gt;IMPORTANT NOTE: this app failed App Policies: 10.5.1 Personal Information. Specifically, it missed an in app privacy policy. I was suggested to add it under section &quot;settings&quot; but there is no sections &quot;settings&quot;, in fact, there are not sections at all, since this is not running in a window.&lt;br&gt;
I have added a privacy policy tappable text in the help screen. You can access that by saying &quot;help&quot; or &quot;show help&quot;, after you have initially placed the airport. This opens a browser and shows the privacy policy.&lt;/strong&gt;&quot;&lt;/p&gt;&lt;h2&gt;Proof of the pudding&lt;/h2&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20200115/processed.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;As you can see, following this procedure took me through Store validation successfully. In fact, it took me through it twice - as it&#39;s &lt;a href=&quot;https://www.microsoft.com/store/productId/9PDJ25RKJNQJ&quot; target=&quot;_blank&quot;&gt;Atlanta twin app ATL HoloATC&lt;/a&gt; got certified within hours after submitting with this addition.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Resuming:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Have some way in your app to popup the privacy policy web page.&lt;/li&gt;&lt;li&gt;Explain in Submission options to the testers where they can find it.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We can discuss at length how useful things like this are, if and how this could be communicated better to developers, if there needs to be guidance first before this is implement for the nice market of &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;Mixed Reality&lt;/a&gt; apps - that don&#39;t have a window nor a settings section - whatever, this works, and while there is no official guidance on how to deal with privacy policies in apps like this, this article show you a way forward. Presumably, for the testers it&#39;s also just simply a matter of being mandated to tick off a box required by the legal department. And this is apparently an acceptable way of getting the box ticked. Job done. &lt;/p&gt;&lt;p&gt;No code sample this time - I assume everyone making MR apps will be able to re-create the two lines of actual code in the behaviour. And possible come up with a more elegant solution. But that was not the point of this - the point was getting in the store without a legal blocker.&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2020/01/mixed-reality-apps-failing-ms-store.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-9219931848800298383</guid><pubDate>Sun, 24 Nov 2019 15:17:00 +0000</pubDate><atom:updated>2019-11-24T16:19:06.151+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRKT2 - Interaction with irregular or complex objects</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;Interaction with objects that are not simple shapes like cubes, spheres, capsules etc. poses some challenges. The &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;&lt;a href=&quot;https://github.com/Microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit&lt;/a&gt; 2 offers some great components, but they all require a top level collider. Now consider this helicopter:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/1_heli.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;108&quot; height=&quot;400&quot; align=&quot;left&quot; style=&quot;margin: 0px 6px 0px 0px; border: 0px currentcolor; border-image: none; float: left; display: inline; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/2_heliobject.png&quot; border=&quot;0&quot;&gt;It consists of a &lt;em&gt;lot&lt;/em&gt; of small objects. Default it does not even &lt;em&gt;have&lt;/em&gt; a collider. You cannot add a Near Interaction Touchable on top of the object because it simply cannot find a collider. Now you can generate those on import, but that makes the object kind of heavy with regards to required processing power, and hooking all those colliders up up to their own Interaction Touchable is a lot of work. &lt;/p&gt;&lt;p&gt;There is a simpler way of doing that, fortunately. Actually, there are two variants of this, but I am going to show the one I think works the best (and is the most beautiful).&lt;/p&gt;&lt;h2&gt;&lt;br&gt;&lt;/h2&gt;&lt;h2&gt;&lt;br&gt;&lt;/h2&gt;&lt;h2&gt;&lt;br&gt;&lt;/h2&gt;&lt;h2&gt;&lt;br&gt;&lt;/h2&gt;&lt;h2&gt;&lt;br&gt;&lt;/h2&gt;&lt;h2&gt;&lt;br&gt;&lt;/h2&gt;&lt;h2&gt;&lt;br&gt;&lt;/h2&gt;&lt;h2&gt;Adding a colliding &#39;catcher&#39; to rule them all&lt;/h2&gt;&lt;p&gt;First of all, we are going to add a surrounding object inside the model itself. I took a capsule, as this gives IMHO the most beautiful result&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;224&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/3_capsuledef.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;The result now is the helicopter is now almost completely covered in what looks like a giant suppository, which is definitely not what you want. &lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;306&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/4_capsule.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;So by fiddling around I created this material. Note: it actual color is fully transparent black - basically 0,0,0,0. so that we can see the helicopter again.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;400&quot; height=&quot;340&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/5_material1.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;But more importantly, it has a hover light override color of green with a light intensity of 0.4.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;400&quot; height=&quot;386&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/6_material2.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt; And now, if a cursor strikes the object, you get this what I think is rather pretty ghostly glow indicating this is your focused object.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;281&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/7_visualcue.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;Making it interactable&lt;/h2&gt;&lt;p&gt;Making it actually interact with events is now pretty simple. Assuming your actual &#39;controller&#39; behaviour needs to control the whole game object, it needs to sit on top - so it can do more things than just interaction (like &lt;em&gt;moving&lt;/em&gt; the helicopter, for instance - which it does not do now). It looks like this:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;using System;
using TMPro;
using UnityEngine;

public class InteractionResponder : MonoBehaviour
{
    [SerializeField]
    private TextMeshPro _text;

    private int _timesClicked;

    private int _timesTouched;

    private int _timesFocus;

    public void Click()
    {
        _timesClicked++;
        UpdateText();
    }
    
    public void TouchStart()
    {
        _timesTouched++;
        UpdateText();
    }

    public void OnFocus()
    {
        _timesFocus++;
        UpdateText();
    }

    private void UpdateText()
    {
        _text.text = string.Format(&quot;Clicked: {0}{1}Touched: {2}{1}Focused: {3}&quot;, 
            _timesClicked, Environment.NewLine, _timesTouched, _timesFocus);
    }
}
&lt;/pre&gt;&lt;p&gt;Super simple, basically only three event response methods that can be called - and it tries to display the text in a TextMeshPro object, that also sits in the HologramCollection, just like the Helicopter it self. As I said, the InteractionResponder behaviour will be sitting on the helicopter object itself:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;582&quot; height=&quot;164&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/8_responder2.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Now we need go back to the SurroundingCapsule and add an Interactable and a Near Interaction Touchable &lt;em&gt;Volume&lt;/em&gt; script to that. The latter is apparently new or something I missed: where an ordinary Near Interaction Touchable only takes a rectangular collider, the Volume script also takes a capsule:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;400&quot; height=&quot;75&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/81_nearintouchvol.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Then, you will need to select &quot;Select&quot; for Input Actions, then drag the helicopter in the little box under OnClick like you usually do in this kind of event hookup, and select ÏnteractionResponder.Click.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;582&quot; height=&quot;480&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/9_eventhookup1.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Then you click &quot;Add Event&quot;, select &quot;InteractableOnTouchReceiver&quot; and hook the On Touch event up to the InteractionResponder.TouchStart method (we will ignore the On Touch End event in this sample).&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;400&quot; height=&quot;211&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/10_eventhookup2.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;In a similar fashion, you will add another event, select &quot;InteractableOnFocusReceiver&quot; and hook that to the InteractionResponder.OnFocus event.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;400&quot; height=&quot;208&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/11_eventhookup3.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And if you have done everything right, and hit the play button in the editor, you will see this happening when you click, touch or focus:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191106/12_finalresult.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;With very little code and one extra component you can make an irregular and complex object like a helicopter have an easy to touch/focus/click target that also gives some subtle but very clear visual feedback about what is happening. And this is only a start - we might as well have it respond to touch by showing some direct feedback, or show that it knows it&#39;s focused, even without a cursor hitting it (eye gaze!). Life is going to get interesting and &lt;em&gt;much&lt;/em&gt; more immersive once &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 2 comes around!&lt;/p&gt;&lt;p&gt;Demo project can be found &lt;a href=&quot;https://github.com/LocalJoost/TopLevelCollider.git&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/11/migrating-to-mrkt2-interaction-with.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-4531079482129288530</guid><pubDate>Mon, 18 Nov 2019 15:53:00 +0000</pubDate><atom:updated>2019-11-18T17:00:10.151+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRKT2 - using extension services for dependency injection</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;Coming from business development, you might get a little shock coming into Unity - traditionally, game developers are much more focused on making the outside pretty than the inside. Things like dependency injection are kind of unheard of or considered &#39;too heavy&#39; for game development. But if you are still in the process of development,&amp;nbsp; actually being able to access a consistent (mock) data service in stead of the real live data service might be a big advantage, especially when that data service is rate limited or expensive.&lt;/p&gt;&lt;p&gt;The &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;&lt;a href=&quot;https://github.com/Microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit&lt;/a&gt; 2 offers a great feature for that: extension services. And it&#39;s actually pretty easy to use, and I am going to show a simple sample. I have written about this in very early alpha stage almost a year ago, but it&#39;s now to a point that it&#39;s actually usable.&lt;/p&gt;&lt;h2&gt;Setting the stage&lt;/h2&gt;&lt;p&gt;Using Unity 2018.4.6f1, I created a simple project MKRT2DepInject using the 3D template, imported the MRKT2 and TextMeshPro. For the latter I usually take essential resources only.Then I and added the MRKT2 to the SampleScene in the project. For the default profile, I usually take the DefaultHololens2Profile. Also, don&#39;t forget to set the platform to UWP (File/Build settings)&lt;/p&gt;&lt;p&gt;Also - and this is important - &lt;a href=&quot;https://dotnetbyexample.blogspot.com/2019/05/migrating-to-mrtk2newtonsoftjson-aka.html&quot; target=&quot;_blank&quot;&gt;import JSON.net from the Unity store&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;Extension services&lt;/h2&gt;&lt;p&gt;A service requires an interface, an implementing class, optionally an inspector, a profile, and a default profile asset. Now the latter three may sounds maybe a bit abstract but it actually boils down to this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;An &lt;em&gt;inspector&lt;/em&gt; is something that can be used to show the runtime status of a service in the editor. It&#39;s basically a debugging tool. It&#39;s entirely optional and in most cases it&#39;s not necessary.&lt;/li&gt;&lt;li&gt;A &lt;em&gt;profile&lt;/em&gt; is a class holding configuration info for a class. If you have been using the MRKT2 for a while, you have been using them all along - cloning profiles and changing settings. &lt;/li&gt;&lt;li&gt;a default service profile asset is basically a serialized version of a profile class. &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This may seem like a lot of work, but there&#39;s actually a nice tool for generating the boiler plate for all that - although had to get in a few pull request myself to getting it to work as I assume was intended ;)&lt;/p&gt;&lt;h2&gt;Creating an extension service&lt;/h2&gt;&lt;p&gt;Select &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;Mixed Reality&lt;/a&gt; Toolkit/Utilities/Create Extension Service. This will bring up this UI:&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/createextension.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;Name the service &quot;DataService&quot;. You will notice the &quot;Service&quot; suffix is mandatory. Choose &quot;Services&quot; for namespace. Then click the &quot;Next&quot; button. This will show you the next stage. &lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/defineextension.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;Now I like to organize my stuff a little, so I tend to put things in folders. The scripts go in a scripts/services folder, the profile in profile. You can set this by dragging the folder from the assets. Notice also I have disabled the inspector:&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img width=&quot;640&quot; height=&quot;466&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/setoptions.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;Hit next, and on the next screen click &lt;em&gt;not now&lt;/em&gt; because otherwise you will be editing the default profiles - effectively, you are modifying the &lt;em&gt;default settings &lt;/em&gt;of the MRKT2. You can do this &lt;em&gt;only&lt;/em&gt; after you have cloned the proper profiles.&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/registerservice.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;You will also notice that although you specified the default asset should have been created in the Profiles folder, it is in fact created in the Services folder. Look I am going to need to make another pull request. Anyway. I moved the DefaultDataServiceProfile to profiles, and let it sit there&lt;/p&gt;&lt;h2&gt;Registering the service&lt;/h2&gt;&lt;p&gt;First, we clone the top profile. &lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/clonetoolkitprofile.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;Then we disable the profiler, because that&#39;s annoyingly in the way when you want to demo something&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/disablediag.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;Then we select the Extensions tab, and clone the &quot;DefaultMixedRealityRegisteredServiceProvidersProfile&quot; (the creators of the MRTK2 seem to have taken a liking to rather verbose names, as you might have noticed) to MyMixedRealityRegisteredServiceProvidersProfile&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/cloneregisteredprovider.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;Now you can actually click the &quot;+ Register a new Service Provider&quot; button and register the service&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img width=&quot;544&quot; height=&quot;297&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/registerservicecomponent1.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;Then you have to click the Configuration Profile drop down, which unfortunately shows you all possible profiles, and you have to pick the one you need, which is DefaultDataServiceProfile, which is fortunately at the top of the list&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/registerservicecomponent2.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;The end result should look like this:&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/registerservicecomponent3.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;Now the configuration stuff is finally done, and we are going to add some code.&lt;/p&gt;&lt;h2&gt;The data and the data set&lt;/h2&gt;&lt;p&gt;My simple sample is going to read a json file from the web and show the contents in the text. Therefore we need a data file, and a class to deserialize it in.&lt;/p&gt;&lt;p&gt;The data file sits &lt;a href=&quot;https://www.schaikweb.net/demo/DemoData.json&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;, and the class in which in can be deserialized looks like this&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;using Newtonsoft.Json;

namespace Json
{
    public class DemoData
    {
        [JsonProperty(&quot;firstName&quot;)]
        public string FirstName { get; set; }

        [JsonProperty(&quot;lastName&quot;)]
        public string LastName { get; set; }
    }
}&lt;/pre&gt;&lt;h2&gt;Configuration profile&lt;/h2&gt;&lt;p&gt;So to make the configuration profile actually configurable, the DataServiceProfile class needs to be changed. We actually need to make a property to store an URL in. So, we add a serializable field and a read only property. Like this:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;using System;
using UnityEngine;
using Microsoft.MixedReality.Toolkit;

namespace Services
{
    [MixedRealityServiceProfile(typeof(IDataService))]
    [CreateAssetMenu(fileName = &quot;DataServiceProfile&quot;, 
        menuName = &quot;MixedRealityToolkit/DataService Configuration Profile&quot;)]
    public class DataServiceProfile : BaseMixedRealityProfile
    {
&lt;strong&gt;&lt;font color=&quot;#ff0000&quot;&gt;        [SerializeField]
        private string _dataUrl;

        public string DataUrl =&amp;gt; _dataUrl;&lt;/font&gt;&lt;/strong&gt;
    }
}&lt;/pre&gt;&lt;p&gt;Added code in red/bold. If you go back to the inspector, you will see there is a Data Url field now added to the DataService profile.&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img width=&quot;500&quot; height=&quot;271&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/addedconfigfield.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;So let&#39;s clone that default profile to SchaikwebProfile:&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img src=&quot;https://www.schaikweb.net/blog/20190311/schaikwebprofile.png&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;And enter for Data Url: &lt;a title=&quot;https://www.schaikweb.net/demo/DemoData.json&quot; href=&quot;https://www.schaikweb.net/demo/DemoData.json&quot;&gt;https://www.schaikweb.net/demo/DemoData.json&lt;/a&gt;. Result: &lt;/p&gt;&lt;p&gt;&lt;img width=&quot;493&quot; height=&quot;270&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/schaikwebprofileselected.png&quot; border=&quot;0&quot;&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;You can now already see how you can quickly change from one configuration profile to another. You could actually clone the schaikwebprofile to another profile with different settings. Now it has only one property, but it can have a lot - and you can change from one setting to another just by selecting a new profile.&lt;/p&gt;&lt;h2&gt;Implementing the actual service&lt;/h2&gt;&lt;p&gt;The generated code for the service - a bit abbreviated - looks like this:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;namespace Services
{
    [MixedRealityExtensionService(....
    public class DataService : BaseExtensionService, IDataService, 
      IMixedRealityExtensionService
    {
        private DataServiceProfile dataServiceProfile;

        public DataService(IMixedRealityServiceRegistrar registrar, ....) 
        {
            dataServiceProfile = (DataServiceProfile)profile;
        }

        public override void Initialize()
        {
            // Do service initialization here.
        }

        public override void Update()
        {
            // Do service updates here.
        }
    }
}&lt;/pre&gt;&lt;p&gt;You can see the profile - the class holding the settings - is being fed into the constructor. Now we don&#39;t need Initialize and Update in this simple service, so we delete that and add this:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;public async Task&amp;lt;IList&amp;lt;DemoData&amp;gt;&amp;gt; GetNames()
{
    using (var request = new HttpRequestMessage(HttpMethod.Post, 
                                                dataServiceProfile.DataUrl))
    {
        using (var client = new HttpClient())
        {
            var response = await client.SendAsync(request);
            response.EnsureSuccessStatusCode();
            var result = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject&amp;lt;IList&amp;lt;DemoData&amp;gt;&amp;gt;(result);
        }
    }
}&lt;/pre&gt;&lt;p&gt;Notice feeding in the URL from the dataserviceProfile!&lt;/p&gt;&lt;p&gt;Of course, we need to add this method to the IDataService interface as well:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;public interface IDataService : IMixedRealityExtensionService
{
    Task&amp;lt;IList&amp;lt;DemoData&amp;gt;&amp;gt; GetNames();
}&lt;/pre&gt;&lt;h2&gt;And now some action...&lt;/h2&gt;&lt;p&gt;So I created this little MonoBehaviour that actually accesses and uses the service.&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;public class NamesReader : MonoBehaviour
{
    [SerializeField]
    private TextMeshPro _text;

    private IDataService _dataService;
    void Start()
    {
        _dataService = MixedRealityToolkit.Instance.GetService&amp;lt;IDataService&amp;gt;();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            LoadNames();

        }
        if (Input.GetKeyDown(KeyCode.Alpha4))
        {
            _text.text = &quot;&quot;;
        }
    }

    private async Task LoadNames()
    {
        var names = await _dataService.GetNames();
        _text.text = string.Join(Environment.NewLine,
            names.Select(p =&amp;gt; $&quot;{p.FirstName} {p.LastName}&quot;));
    }
}&lt;/pre&gt;&lt;p&gt;You can see how it simply gets a reference to the service in the start method. If you run this in the editor and you press &quot;3&quot; it will try to load the values from the service, and show them in as TextMeshPro _text (pressing &quot;4&quot; clears it again). The extremely spectacular result looks like this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;300&quot; height=&quot;161&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/serviceresult.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Basically a direct dump from the data file on my website:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;[
    {
        &quot;firstName&quot;: &quot;Scott&quot;,
        &quot;lastName&quot;: &quot;Guthrie&quot;
    },
    {
        &quot;firstName&quot;: &quot;Alex&quot;,
        &quot;lastName&quot;: &quot;Kipman&quot;
    },
    {
        &quot;firstName&quot;: &quot;Scott&quot;,
        &quot;lastName&quot;: &quot;Hanselman&quot;
    }
]&lt;/pre&gt;&lt;h2&gt;Mocking service access&lt;/h2&gt;&lt;p&gt;Now let&#39;s assume, for the moment, this data service is extremely expensive, slow or otherwise limited in access. Or you need to test certain edge cases but the data service does not always give them when you need them. In other words, you want to make a fake service - a &lt;a href=&quot;https://en.wikipedia.org/wiki/Mock_object&quot; target=&quot;_blank&quot;&gt;mock&lt;/a&gt; service. This, now, is very simple.&lt;/p&gt;&lt;p&gt;So let&#39;s build a mocking service:&lt;/p&gt;&lt;pre&gt;[MixedRealityExtensionService(....
public class MockDataService : BaseExtensionService, 
                               IDataService
{
    public MockDataService(IMixedRealityServiceRegistrar registrar, ....
    {
    }

    public async Task&amp;lt;IList&amp;lt;DemoData&amp;gt;&amp;gt; GetNames()
    {
        var data = new List&amp;lt;DemoData&amp;gt;
        {
            new DemoData {FirstName = &quot;Joost&quot;, LastName = &quot;van Schaik&quot;},
            new DemoData {FirstName = &quot;John&quot;, LastName = &quot;Doe&quot;},
            new DemoData {FirstName = &quot;Kermit&quot;, LastName = &quot;the Frog&quot;},
        };
        await Task.Yield();
        return data;
    }
}
&lt;/pre&gt;&lt;p&gt;So we implement the same interface, but it does not take a DataServiceProfile configuration (although it perfectly could if I implemented the constructor). And now a second implementation version of the service appears in the drop down:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;285&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/mockservice2.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Sow you can quickly now change a single service from a production implementation to test implementation. The mock service will show this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;300&quot; height=&quot;140&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/mockserviceresult.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;But what is even more cool is when you make a &#39;mock profile&#39; from the RegisteredServiceProfile profile. For if you have like 20 services (and believe me, the number of services goes up pretty quickly) you can change from test to production by simply switching the profile. So I cloned the MyMixedRealityRegisteredServiceProvidersProfile itself to MockMixedRealityRegisteredServiceProvidersProfile and now, by simply switching profiles - you can change the whole extension service definition with one simple dropdown.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;219&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/mockserviceprofile.png&quot; border=&quot;0&quot;&gt;&lt;img width=&quot;500&quot; height=&quot;289&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190311/realserviceprofile.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Extension services are a really powerful feature of the MRTK2, that can be used for central access of data services - typically stuff you would use Singletons for in ye olde HoloToolkit. But using service profiles also offers a quick and easy way to switch between real and mock implementations, brings an important part of enterprise level development into the traditional - ahem - more chaotic Unity development environment.&lt;/p&gt;&lt;p&gt;Demo project &lt;a href=&quot;https://github.com/LocalJoost/MKRT2DepInject.git&quot; target=&quot;_blank&quot;&gt;can be found here&lt;/a&gt;.&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/11/migrating-to-mrkt2-using-extension.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-72936390822227150</guid><pubDate>Sun, 17 Nov 2019 17:04:00 +0000</pubDate><atom:updated>2019-11-17T18:04:56.164+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRKT2 - making a &#39;hand palm&#39; menu</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;In all modesty I think I pretty much nailed making simple &#39;user interfaces&#39; for &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 1 - simple dialogs and stuff. Clients told me &#39;even a moron can understand how to operate this&#39; so I think I did allright. But stuff floating in thin air wasn&#39;t always ideal. And now with HoloLens 2, we can actually let stick stuff to hands, in stead of just the gaze. So I decide to check if I could make a menu that stuck in the palm of your hand. Turned out I could.&lt;/p&gt;&lt;p&gt;And it&#39;s ridiculously easy to boot.&lt;/p&gt;&lt;h2&gt;Creating a hand palm menu&lt;/h2&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191107/1_Handmenu.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;This was actually just built from the UX components in the MRKT2. &lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191107/2_component.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;These are literally only two &quot;PressableButtonHoloLens2&quot; prefabs floating about 2cm before a quad that has this material:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191107/3_backplatematerial.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Then I added a Solver Handler to the HandMenu&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191107/4_solverhandler.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;I had to fiddle a bit with the settings, especially the additional offset - to determine where exactly the menu is going to appear in relation to the palm. How these parameters exactly work is a bit unclear with me, so I used the scientific method: I started to change the numbers till things happened the way I liked ;)&lt;/p&gt;&lt;p&gt;And finally I added a Hand Constraint Palm Up with these settings&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://www.schaikweb.net/blog/20191107/5_handconstraint.png&quot;&gt;&lt;/p&gt;&lt;p&gt;And this is basically all you have to do to have the menu appear.&lt;/p&gt;&lt;h2&gt;Some configuration settings&lt;/h2&gt;&lt;p&gt;Although this works with the &#39;normal&#39; simulated hand setting as well, it&#39;s best viewed with the &#39;flat&#39; hand palm. To get this, starting with&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The Mixed Reality Toolkit Profile (Cloned from DefaultHoloLens2ConfigurationProfile)&lt;/li&gt;&lt;li&gt;The Input System Profile&lt;/li&gt;&lt;li&gt;The Input Simulation Profile&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Then you have to expand the &quot;Input Simulation Service&quot; section, scroll all the way down to &quot;Hand Gesture Settings&quot; and change the settings there as follows:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20191107/6_gesturesettings.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;These are my default settings - but for this demo actually only the top setting is important (Default Hand Gesture to &quot;Flat&quot;)&lt;/p&gt;&lt;h2&gt;Testing the menu&lt;/h2&gt;&lt;p&gt;I think it&#39;s safe to say that most of the people reading this - including me - don&#39;t have a HoloLens 2. Fortunately you can test this quite easily in the editor. To get to the point shown in the first picture in this post, simply do the following:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Start play mode&lt;/li&gt;&lt;li&gt;Move the mouse cursor inside the game window&lt;/li&gt;&lt;li&gt;Press the Space bar and keep it down- the right hand should now appear&lt;/li&gt;&lt;li&gt;While keeping the space bar pressed down, also press the left control key on your keyboard and keep it down&lt;/li&gt;&lt;li&gt;With your other hand, slowly move the mouse to the left. The hand should now start to rotate in stead of move&lt;/li&gt;&lt;li&gt;When you have rotated the hand just past the 90° angle, the menu should appear.&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;Making it actually do something&lt;/h2&gt;&lt;p&gt;You might have noticed the buttons don&#39;t do anything at this stage. So I created a little helper behaviour that shows &quot;Yes&quot; or &quot;No&quot; depending on which button you press. It&#39;s not very sophisticated:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;It simply follows the actual hand menu around&lt;/li&gt;&lt;li&gt;It has a public method &quot;ShowYes&quot; and &quot;ShowNo&quot; that can be hooked up to the buttons to show something when they are pressed.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;It sits in a &quot;ResponseHelper&quot; game object and the hookup to the button is therefore simple, as the YesButton shows:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://www.schaikweb.net/blog/20191107/7_buttonhookup.png&quot;&gt;&lt;/p&gt;&lt;p&gt;The actual working is displayed in this little video:&lt;/p&gt;&lt;iframe width=&quot;650&quot; height=&quot;365&quot; src=&quot;https://www.youtube.com/embed/YEHY88zZGI4&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot;&gt;&lt;/iframe&gt;&lt;p&gt;The trick to make the right hand stay while you operate the left hand is:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Press Y once the right hand is in the desired position and rotation&lt;/li&gt;&lt;li&gt;Release space bar and control key (right hand stays where it is, you are just longer controlling it)&lt;/li&gt;&lt;li&gt;Press the left shift key - the left hand should now appear&lt;/li&gt;&lt;li&gt;While keeping the left shift key pressed, you can move the left hand with the mouse. To move it forward and backward like in the movie, rotate the mouse wheel. This way you can actually make the index finger touch the buttons&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Building a hand palm menu is really easy and basically requires no code. How this will work on a real HoloLens 2 - only time will tell. In the mean time you can find the demo project &lt;a href=&quot;https://github.com/LocalJoost/HandPalmMenu&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/11/migrating-to-mrkt2-making-hand-palm-menu.html</link><author>noreply@blogger.com (Joost van Schaik)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/YEHY88zZGI4/default.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-8302476992650493302</guid><pubDate>Thu, 26 Sep 2019 18:33:00 +0000</pubDate><atom:updated>2019-09-27T06:44:19.850+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRTK2 - setting up and understanding Eye Tracking</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;One of the exiting features &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 2 brings us, is Eye Tracking. On HoloLens 1, you had to move your whole head to move the gaze cursor. and while that works well enough for a lot of applications and it seems most people pretty quickly got used to it, mother Nature has equipped us with roving eyes. HoloLens 2, when calibrated for Eye Tracking, can actually track what your are &lt;em&gt;looking&lt;/em&gt; at, not merely where your head is pointed at. &lt;/p&gt;&lt;p&gt;Although there is a nice demo in the &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;&lt;a href=&quot;https://github.com/Microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit&lt;/a&gt; 2, it took me a while to find out how all the events actually work and need to be hooked up to get it to work consistently. So I made a little demo that works like this:&lt;/p&gt;&lt;iframe width=&quot;650&quot; height=&quot;365&quot; src=&quot;https://www.youtube.com/embed/Ych87QTV3EA&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot;&gt;&lt;/iframe&gt;&lt;h2&gt;Events and tracking them&lt;/h2&gt;&lt;p&gt;The little blue globe is the target, is equipped with and EyeTrackingTarget script from the MRTK2 that supports five events, which you can see going off as the red spheres turns green&lt;/p&gt;&lt;ul&gt;&lt;li&gt;LS: On Look At Start&lt;/li&gt;&lt;li&gt;WL: While Looking At Target&lt;/li&gt;&lt;li&gt;LA: On Look Away&lt;/li&gt;&lt;li&gt;DW: On Dwell&lt;/li&gt;&lt;li&gt;S: On Selected&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The EyeTrackingTarget is configured as follows:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;273&quot; height=&quot;400&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://schaikweb.net/blog/20190925/EyeTracking.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;&lt;img align=&quot;left&quot; style=&quot;margin: 0px 9px 0px 0px; border: 0px currentcolor; border-image: none; float: left; display: inline; background-image: none;&quot; src=&quot;https://schaikweb.net/blog/20190925/EyeTrackerScene.png&quot; border=&quot;0&quot;&gt;In the scene, the whole thing showing the images (the five little red-turning-green globes with labels) is one prefab containing 5 little spheres with a label above it - each a prefab on its own. Every sphere has a &quot;Single Shot Controller&quot; script that turns it&#39;s sphere green for 0.5 seconds when an event is called.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;235&quot; height=&quot;100&quot; style=&quot;border: 0px currentcolor; border-image: none; display: inline; background-image: none;&quot; src=&quot;https://schaikweb.net/blog/20190925/SingleShotController.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;It&#39;s a super simple script, the interesting part is even shorter:&lt;/p&gt;&lt;pre&gt;public void ShowActivated()
{
    _timeActivated = Time.time;
}

void Update()
{
    var desiredColor = Time.time - _timeActivated &amp;gt; _resetTime ? 
        _originalColor : _activatedColor;
    if (_material.color != desiredColor)
    {
        _material.color = desiredColor;
    }
}&lt;/pre&gt;&lt;p&gt;When ShowActivated is called, the _timeActivated field is set to now. The Update loop then checks every 60th of a second whether it should set the color to red or green, depending on the fact if the latest call to ShowActivated is already half a second ago.&lt;/p&gt;&lt;h2&gt;What happens when&lt;/h2&gt;&lt;p&gt;The event names are pretty straightforward, and things happen more or less than you expect, although &lt;/p&gt;&lt;p&gt;What is actually happening:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;When the user first looks at the eye tracked object, &quot;On Look Start&quot; is fired once&lt;/li&gt;&lt;li&gt;&lt;em&gt;While&lt;/em&gt; the user keeps looking, &quot;While Looking At Target&quot; &lt;em&gt;keeps being called. &lt;/em&gt;Thus, the green sphere stays green. The calling seems to at the same instant - or nearly the same instant - as the previous event&lt;/li&gt;&lt;li&gt;As soon as the user stops looking at the sphere, &quot;On Look Away&quot; is called and &quot;While Looking At Target&quot; is stopped being called&lt;/li&gt;&lt;li&gt;&quot;On Dwell&quot; is being called after the time defined in the &quot;Dwell Time in Sec&quot; slider has passed has the user is &lt;em&gt;still&lt;/em&gt; looking at the object. I took the ridiculously user-unfriendly time of three seconds to make sure this event was easily distinguishable from the other events. Here&#39;s the thing though - it&#39;s being called &lt;em&gt;once&lt;/em&gt;. That kind of confused me.&lt;/li&gt;&lt;li&gt;&quot;On Selected&quot; is being called when then object being looked at &lt;em&gt;and&lt;/em&gt; you say &quot;Select&quot;. This is one of the predefined commands in the default speech commands profile (DefaultMixedRealitySpeechCommandsProfile)&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Setting up and configuring eye tracking in profiles&lt;/h2&gt;&lt;p&gt;Coming from the default profile, you will need to configure at least profiles, and better still three.&lt;/p&gt;&lt;p&gt;First, you will need to clone the Default Toolkit profile itself. First thing I do, while still in the early phases, is disabling the diagnostics system as I don&#39;t want that profiler in my face the whole time:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;366&quot; height=&quot;100&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190925/diagnostics.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Next, you will have to clone the Input System Profile and add a Windows Mixed Reality Eye Gaze Provider&quot; &lt;/p&gt;&lt;p&gt;&lt;img width=&quot;500&quot; height=&quot;565&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://schaikweb.net/blog/20190925/ConfigureEyeGazer.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;As you can see, this sits in namespace &quot;Microsoft.MixedReality.Toolkit.WindowsMixedReality.Input&quot;&lt;/p&gt;&lt;p&gt;And then finally, and optionally, if you want this to work in the editor too, you will have to configure the Input Simulation Service. You do that by cloning the default input system profile and check the &quot;Simulate Eye Position&quot; checkbox &lt;/p&gt;&lt;p&gt;&lt;img width=&quot;301&quot; height=&quot;500&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190925/SimulateEyePos.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;One more thing: setting capabilities&lt;/h2&gt;&lt;p&gt;&lt;img width=&quot;288&quot; height=&quot;300&quot; align=&quot;left&quot; style=&quot;margin: 0px 9px 0px 0px; border: 0px currentcolor; border-image: none; float: left; display: inline; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190925/capabilities.png&quot; border=&quot;0&quot;&gt;You will notice now the Gaze cursor turning up in the editor so you might thing you are done. Well, almost. There&#39;s the small matter of capabilities. C++ or not, the result is still a UWP app, and Gaze Input is a capability that you need to ask consent for. This, unfortunately, is not yet implemented in Unity. So after you generated the C++ app, you will need to open it in Visual Studio, select the Package.appmanifest file, and select &lt;em&gt;there&lt;/em&gt; the Gaze Input capability.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;img width=&quot;203&quot; height=&quot;127&quot; align=&quot;left&quot; style=&quot;margin: 0px 9px 0px 0px; border: 0px currentcolor; border-image: none; float: left; display: inline; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190925/consent.png&quot; border=&quot;0&quot;&gt;If you deploy the resulting solution to an emulator (or, if you are one of the lucky ones out there, an actual HoloLens 2) and it asks for your consent, you did it right.&lt;/p&gt;&lt;h2&gt;Conclusion and final words&lt;/h2&gt;&lt;p&gt;Setting up Eye Tracking is not that hard, but it takes a few steps. Mind you the MRTK2 comes with a few profiles that make settings things up easier - I just wrote down the steps from scratch. &lt;a href=&quot;https://github.com/LocalJoost/EyeTrackingDemo&quot; target=&quot;_blank&quot;&gt;The demo project&lt;/a&gt; shows this in all it&#39;s glory ;) and allows you to play with it yourself without having to set it up. Notice there&#39;s hardly any code outside of the MRKT2 itself involved - there&#39;s only one custom script (my SingleShotController) and that&#39;s very simple.&lt;/p&gt;&lt;p&gt;By the way - in my own (so far single) HoloLens 2 app I only use the On While Looking event. This seems to be the most trustworthy. I previous iterations of the MRTK2 and/or the HoloLens emulator the other events did not go off reliably enough (IMHO) to use them for real. Of course, this may all be different now, and most likely is completely different (better) on a real device. We are still waiting for that. &lt;/p&gt;&lt;p&gt;On a final note – leaving the eye cursor visible can be confusing and/or annoying, or so I have been told. So under normal circumstances, it should be turned off – the object being looked should give some indication it is notices being looked at. I have found a way to do this myself, but that’s pretty complex, and just as I was about to blog about that, &lt;a href=&quot;https://twitter.com/julenka&quot; target=&quot;_blank&quot;&gt;Julia Schwarz&lt;/a&gt; herself added &lt;a href=&quot;https://github.com/microsoft/MixedRealityToolkit-Unity/blob/mrtk_development/Assets/MixedRealityToolkit.Examples/Demos/Input/Scenes/DisablePointers/DisablePointersExample.cs&quot; target=&quot;_blank&quot;&gt;a (better) sample to turn off pointers by code&lt;/a&gt; to the main MRTK2 repo.&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/09/migrating-to-mrtk2-setting-up-and.html</link><author>noreply@blogger.com (Joost van Schaik)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/Ych87QTV3EA/default.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-8306438080166903768</guid><pubDate>Thu, 05 Sep 2019 18:58:00 +0000</pubDate><atom:updated>2019-09-05T20:59:28.526+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">Microsoft Store</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRTK2–submitting a HoloLens 2 app to the Microsoft Store</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;On Monday September 2 version 4.0.19 of my first &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; Store app, &lt;a href=&quot;https://www.microsoft.com/store/productId/9P6SVQQCP2SQ&quot; target=&quot;_blank&quot;&gt;AMS HoloATC&lt;/a&gt;, became available in the Microsoft Store. This version has been completely rebuilt using the &lt;a href=&quot;https://github.com/microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit 2&lt;/a&gt;, and includes some HoloLens 2–only functionality: you can actually touch the airplanes now, and using gaze tracking it will show you a picture of the actual aircraft, if available. &lt;/p&gt;&lt;iframe width=&quot;650&quot; height=&quot;365&quot; src=&quot;https://www.youtube.com/embed/pwE3SPLB8Ko&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot;&gt;&lt;/iframe&gt;&lt;h2&gt;Hoops to jump&lt;/h2&gt;&lt;p&gt;As you might recall from earlier posts, things have changed quite a bit when it comes to actually deploying apps on HoloLens. Now, we will need compiling and submitting a Unity-generated C++ solution to the store. Although the process looks very much like we used to do for Unity apps running on the .NET backend, there are three things you might run into:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Your WACK test will most likely fail&lt;/li&gt;&lt;li&gt;If you have submitted your app as a bundle before, make sure you submit it as a bundle again. A Unity generated C++ solution does not have this as a default setting&lt;/li&gt;&lt;li&gt;If you create (like me) an app that is supposed to run on Desktop (in immersive head sets), HoloLens 1 &lt;em&gt;and&lt;/em&gt; HoloLens 2 you may find out your app cannot be downloaded by a HoloLens 1 anymore – or the HoloLens 2 emulator, for what matters&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;Fixing the WACK fail&lt;/h2&gt;&lt;p&gt;Before you actually submit an app to the Store, you do the Windows Application Certification test first, to prevent embarrassing easy-to-prevent fails, right. (&lt;em&gt;right?). &lt;/em&gt;And if you do so, you will see it fail. It will spout quite some errors at you.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The Windows security features test will complain about:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;HolographicAppRemoting.dll has failed the AppContainerCheck check.&lt;/li&gt;&lt;li&gt;
PerceptionDevice.dll has failed the AppContainerCheck check.&lt;br&gt;&lt;/li&gt;&lt;li&gt;UnityRemotingWMR.dll has failed the AppContainerCheck check.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;The Supported API test will list 10 errors concerning UnityRemotingWMR calling unsupported APIs&lt;/li&gt;&lt;li&gt;The Debug configuration test will tell youUnity RemotingWMR is only built in debug mode&lt;/li&gt;&lt;li&gt;And if you try to build for x86 or ARM, the Package sanity test will tell you HolographicAppRemoting.dll, PerceptionDevice.dll and UnityRemotingWMR.dll are only available for x64.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The solution is bit weird, but can be found &lt;a href=&quot;https://forum.unity.com/threads/hololens-appx-built-with-2018-4-3f1-fails-store-submission.706994/&quot; target=&quot;_blank&quot;&gt;in this Unity forum post&lt;/a&gt;, and involves manually hacking the “Unity Data.vcxitems” file that is inside your store projects. Open in it in a text editor, and search for “HolographicAppRemoting”. This will show this piece of XML:&lt;/p&gt;&lt;pre&gt;&amp;lt;None Include=&quot;$(MSBuildThisFileDirectory)HolographicAppRemoting.dll&quot;&amp;gt;
  &amp;lt;DeploymentContent&amp;gt;true&amp;lt;/DeploymentContent&amp;gt;
  &amp;lt;ExcludeFromResourceIndex&amp;gt;true&amp;lt;/ExcludeFromResourceIndex&amp;gt;
&amp;lt;/None&amp;gt;&lt;/pre&gt;&lt;p&gt;Now simply change the value “true” inside the DeploymentContent to false:&lt;/p&gt;&lt;pre&gt;&amp;lt;None Include=&quot;$(MSBuildThisFileDirectory)HolographicAppRemoting.dll&quot;&amp;gt;
  &amp;lt;DeploymentContent&amp;gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;strong&gt;false&lt;/strong&gt;&lt;/font&gt;&amp;lt;/DeploymentContent&amp;gt;
  &amp;lt;ExcludeFromResourceIndex&amp;gt;true&amp;lt;/ExcludeFromResourceIndex&amp;gt;
&amp;lt;/None&amp;gt;&lt;/pre&gt;&lt;p&gt;Repeat this for PerceptionDevice and UnityRemotingWMR. Rebuild your app, generated packages again and presto, your WACK test will pass. That is literally all that’s needed to get rid of this multitude of errors. &lt;/p&gt;&lt;h2&gt;Bundling your app&lt;/h2&gt;&lt;p&gt;I don’t know exactly what changed, but all my HoloLens apps that I created with previous Unity versions were uploaded as bundles. To be honest, I never did pay much attention to it. But the default setting of the generated C++ setting is this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;562&quot; height=&quot;480&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190905/Bundle.PNG&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;which generates an appx per platform (in my case three: one for x64, one for x86 and one for ARM for, in the same order, WMR immersive headsets, HoloLens 1 and HoloLens 2). If you try to upload those files as updates to an app that was previously submitted as a bundle you will be greeted with:&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img width=&quot;562&quot; height=&quot;139&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190905/Error.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;And this can simply be fixed by changing the setting “Generate app bundle” from “If needed” to “Always”&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;img width=&quot;122&quot; height=&quot;50&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190905/Bundle2.png&quot; border=&quot;0&quot;&gt;&lt;/font&gt;&lt;/p&gt;&lt;h2&gt;Make your app (still) downloadable for HoloLens 1&lt;/h2&gt;&lt;p&gt;To be honest, 4.0.19 was not the first HoloLens 2 enabled version I submitted. That was 4.0.17. It got certified – as one of the first if not the very first indie HoloLens 2 app. I was very happy about this – for about 25 seconds. And then I got a very unpleasant surprise: I could not download it anymore on a HoloLens 1. Sure enough, you could find it in the store, but the “Install” button was greyed out (well, light blue in stead of dark blue, but in any case unoperational). Curiously enough, a HoloLens that had it already installed did get the updated version though. &lt;/p&gt;&lt;p&gt;The reason for this behavior can be found down in &lt;a href=&quot;https://forum.unity.com/threads/unable-to-download-hololens-app-from-microsoft-store-possible-unity-version-connection.640720/&quot; target=&quot;_blank&quot;&gt;this post in the Unity forums&lt;/a&gt;. Basically, Unity dropped support for anything lower than DirectX 10 and this is listed now in the app’s store manifest. Unfortunately, when the Store on the HoloLens 1 (and the HoloLens 2 emulator, incidentally) checks for DirectX 10, the device apparently reports “don’t have that” and the Store consequently blocks download.&lt;/p&gt;&lt;p&gt;Now I think this will be fixed shortly, but in the mean time here’s a workaround for if you need do to a submission right now:&lt;/p&gt;&lt;p&gt;First, open the Package.AppManifest.xml file in a text editor. Find these lines:&lt;/p&gt;&lt;pre style=&quot;font-size: 10px;&quot;&gt;&amp;lt;TargetDeviceFamily Name=&quot;Windows.Desktop&quot; MinVersion=&quot;10.0.16299.0&quot; MaxVersionTested=&quot;10.0.18362.0&quot; /&amp;gt;
&amp;lt;TargetDeviceFamily Name=&quot;Windows.Holographic&quot; MinVersion=&quot;10.0.16299.0&quot; MaxVersionTested=&quot;10.0.18362.0&quot; /&lt;/pre&gt;&lt;p&gt;Comment out the &lt;em&gt;second&lt;/em&gt; line. Then proceed to build a bundle, but for x64 &lt;em&gt;only&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Go back to Package.AppManifest.xml, uncomment the &lt;em&gt;first&lt;/em&gt; line, and re-activate the second. Now find the StoreManifest.xml file – open it in a text editor. It should look like this:&lt;/p&gt;&lt;pre&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;StoreManifest xmlns=&quot;http://schemas.microsoft.com/appx/2015/StoreManifest&quot;&amp;gt;
    &amp;lt;Dependencies&amp;gt;
        &amp;lt;DirectXDependency Name=&quot;D3D11_HWFL_10_0&quot; /&amp;gt;
    &amp;lt;/Dependencies&amp;gt;
&amp;lt;/StoreManifest&amp;gt;&lt;/pre&gt;&lt;p&gt;Simply remove the line &amp;lt;DirectXDependency Name=&quot;D3D11_HWFL_10_0&quot; /&amp;gt;
&lt;/p&gt;&lt;p&gt;Now build a package for x86 and ARM. I am not sure if this is essential, but I made sure the x86/ARM bundle was had a one release number one point higher than the x64. &lt;/p&gt;&lt;p&gt;Now proceed to upload both bundles into a submission and set check boxes as needed. In my store submissions it looks like this:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;284&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190905/StoreView.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Now as you can see version 4.0.17 still contains &lt;em&gt;all&lt;/em&gt; platforms, but that is not necessary. But because the 4.0.19 has a higher version number, it will be offered first to HoloLens 1 and 2.&lt;/p&gt;&lt;p&gt;Anyway, now your app, once certified, should be downloadable for all devices. On x64 the DirectX 10 check will be still in place, for other devices it’s disabled. &lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;It’s early days for HoloLens 2 (I built my app without having direct access to it) but I think it’s pretty cool to have an app armed and ready for it. It takes some fiddling around with xml files to get it right, but I am sure things will be better soon and these work arounds won’t be necessary anymore. &lt;/p&gt;&lt;p&gt;Enjoy building the next generation Mixed Reality apps!&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/09/migrating-to-mrtk2submitting-hololens-2.html</link><author>noreply@blogger.com (Joost van Schaik)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/pwE3SPLB8Ko/default.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-8926506343654336437</guid><pubDate>Tue, 13 Aug 2019 09:45:00 +0000</pubDate><atom:updated>2019-08-13T11:45:26.804+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRTK2–interacting with the Spatial Map</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;One of the HoloLens’ great features is the ability to interact with real physical objects. This allows apps to place holograms on or adjacent to real objects, enables occlusion (the ability to let holograms appear to be hidden because they disappear behind physical objects), etc. This is all done using the Spatial Map, a graphical representation of whatever the HoloLens has observed to be present in the physical reality. Interacting with the Spatial map used to be easy – and it actually still isn&#39;t that hard, it’s just that - as with most of the things in the &lt;a href=&quot;https://github.com/microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;MRTK2&lt;/a&gt; - quite some cheese has been moved &lt;/p&gt;&lt;p&gt;This blog post handles a common and a not so common scenario for interacting with the Spatial Map:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Placing objects on the Spatial Map&lt;/li&gt;&lt;li&gt;Programmatically enabling and disabling/clearing the Spatial Map&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I have included a &lt;a href=&quot;https://github.com/LocalJoost/SpatialMapInteraction&quot; target=&quot;_blank&quot;&gt;demo project&lt;/a&gt; that allows you to place cylinders on the Spatial Map by air tapping - and you can turn the Spatial Map on and off using a floating button.&lt;/p&gt;&lt;h2&gt;Placing objects on the Spatial Map, MRKT2 style&lt;/h2&gt;&lt;p&gt;I wrote about this already in &lt;a href=&quot;https://dotnetbyexample.blogspot.com/2017/10/finding-floor-and-displaying-holograms.html&quot; target=&quot;_blank&quot;&gt;November 2017&lt;/a&gt; in my article about finding the floor using a HoloLens. In &lt;a href=&quot;https://github.com/microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;MRTK2&lt;/a&gt;, that process is a bit much different. Create a raycast from the Camera along the camera viewing angle and try to hit the Spatial Map. For this, you need the Spatial Map Layer mask. In the HoloToolkit you could simply access. &lt;/p&gt;&lt;pre&gt;SpatialMappingManager.Instance.LayerMask&lt;/pre&gt;&lt;p&gt;to get to that layer mask. Finding that now is a &lt;em&gt;wee&lt;/em&gt; bit more complicated. You see, first, you need to extract the configuration from the Spatial Awareness System service like this: &lt;/p&gt;&lt;pre&gt;var spatialMappingConfig =&lt;br&gt;      CoreServices.SpatialAwarenessSystem.ConfigurationProfile as&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MixedRealitySpatialAwarenessMeshObserverProfile;&lt;/pre&gt;&lt;p&gt;The spatial mapping config contains&lt;em&gt;&amp;nbsp;&lt;/em&gt;a property called ObserverConfigurations containing a &lt;em&gt;list&lt;/em&gt; of of configurations (apparently taking provisions there might actually be more than one configuration). For each configuration you can take the profile from it&#39;s ObserverProfile property - that you have to cast to MixedRealitySpatialAwarenessMeshObserverProfile. Then you find the layer used by this config in it&#39;s MeshPhysicsLayer property.&lt;/p&gt;&lt;p&gt;I repeat - &lt;em&gt;you can find the &lt;strong&gt;layer&lt;/strong&gt;. &lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;That is not the layer &lt;strong&gt;mask&lt;/strong&gt;. &lt;/em&gt;It took me &lt;em&gt;quite&lt;/em&gt; some time debugging to find out what was going on here - because if you feed that layer &lt;em&gt;number &lt;/em&gt;into the raycast, it won&#39;t &#39;see&#39; the Spatial Map. I have no idea why this was changed. Anyway, to get the layer &lt;em&gt;mask, &lt;/em&gt;as required by raycast methods,&lt;em&gt;&amp;nbsp;&lt;/em&gt;you have to &lt;em&gt;bit shift&lt;/em&gt; the actual layer &lt;em&gt;number&lt;/em&gt;, like this&lt;/p&gt;&lt;pre&gt;1 &amp;lt;&amp;lt; observerProfile.MeshPhysicsLayer&lt;/pre&gt;&lt;p&gt;So what used to be a single property, now requires this method:&lt;/p&gt;&lt;pre&gt;private static int GetSpatialMeshMask()
{
    if (_meshPhysicsLayer == 0)
    {
        var spatialMappingConfig = 
          CoreServices.SpatialAwarenessSystem.ConfigurationProfile as
            MixedRealitySpatialAwarenessSystemProfile;
        if (spatialMappingConfig != null)
        {
            foreach (var config in spatialMappingConfig.ObserverConfigurations)
            {
                var observerProfile = config.ObserverProfile
                    as MixedRealitySpatialAwarenessMeshObserverProfile;
                if (observerProfile != null)
                {
                    _meshPhysicsLayer |= (1 &amp;lt;&amp;lt; observerProfile.MeshPhysicsLayer);
                }
            }
        }
    }

    return _meshPhysicsLayer;
}

private static int _meshPhysicsLayer = 0;&lt;/pre&gt;&lt;p&gt;And I added a static backing variable to speed up this process, otherwise this whole loop will be run 60 times a second in my &lt;a href=&quot;https://github.com/LocalJoost/SpatialMapInteraction/blob/master/Assets/App/Scripts/TapToPlaceController.cs&quot; target=&quot;_blank&quot;&gt;TapToPlaceController&lt;/a&gt;, as well as every time you air tap to place a cylinder.&lt;/p&gt;&lt;p&gt;The method to find a point on the Spatial Map simply is then simply this:&lt;/p&gt;&lt;pre&gt;public static Vector3? GetPositionOnSpatialMap(float maxDistance = 2)
{
    RaycastHit hitInfo;
    var transform = CameraCache.Main.transform;
    var headRay = new Ray(transform.position, transform.forward);
    if (Physics.Raycast(headRay, out hitInfo, maxDistance, GetSpatialMeshMask()))
    {
        return hitInfo.point;
    }
    return null;
}&lt;/pre&gt;&lt;p&gt;This sits in the updated &lt;a href=&quot;https://github.com/LocalJoost/SpatialMapInteraction/blob/master/Assets/MRTKExtensions/Utilities/LookingDirectionHelpers.cs&quot; target=&quot;_blank&quot;&gt;LookingDirectionHelpers&lt;/a&gt; class. In the &lt;a href=&quot;https://github.com/LocalJoost/SpatialMapInteraction&quot; target=&quot;_blank&quot;&gt;demo project&lt;/a&gt; you can see how it is actually used. &lt;/p&gt;&lt;p&gt;In the &lt;a href=&quot;https://github.com/LocalJoost/SpatialMapInteraction/blob/master/Assets/App/Scripts/TapToPlaceController.cs&quot; target=&quot;_blank&quot;&gt;TapToPlaceController&lt;/a&gt;, the Update method will flip the text from “Please look at the spatial map max 2m ahead of you&quot; to &quot;Tap to select a location&quot; when the gaze strikes the Spatial Map (and the Spatial Map ONLY, not another hologram).&lt;/p&gt;&lt;pre&gt;protected override void Update()&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; _instructionTextMesh.text = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; LookingDirectionHelpers.GetPositionOnSpatialMap(_maxDistance) != null ? &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &quot;Tap to select a location&quot; : _lookAtSurfaceText;&lt;br&gt;
}&lt;/pre&gt;&lt;p&gt;If you then air tap, it will place a squatted cylinder on the spatial map at the place you are looking to. This is done in the OnPointerDown method - using the same call to LookingDirectionHelpers.GetPositionOnSpatialMap to get a point to place the cylinder.&lt;/p&gt;&lt;p&gt;You will notice a floating cube as well. You can&#39;t place a cylinder on the cube - it only finds the Spatial Map. Demonstrating that you can&#39;t place a cylinder on it, is the cube&#39;s sole purpose ;). What might happen is that you place a cylinder &lt;em&gt;behind the cube on the Spatial Map&lt;/em&gt;, if your opposite wall is closer than 2 meters. It requires additional logic to handle that situation, but that is beyond the scope of this blog post. &lt;/p&gt;&lt;h2&gt;Starting, stopping and clearing the Spatial map&lt;/h2&gt;&lt;p&gt;For some apps, most notably my &lt;a href=&quot;https://www.microsoft.com/store/productId/9P6SVQQCP2SQ&quot; target=&quot;_blank&quot;&gt;AMS HoloATC&lt;/a&gt; app, the Spatial Map is used to help getting an initial place to put an object but then it needs to go away, as to not get the view blocked by occlusion. Making the Spatial Map transparent sometimes helps, but then still the walls get in the way of selecting objects as they block the gaze and other cursors. Long story short – it is sometimes desirable to be able to turn the Spatial map on and off. And this is actually pretty simple:&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;pre&gt;public void ToggleSpatialMap()&lt;br&gt;{    &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if( CoreServices.SpatialAwarenessSystem != null)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if( IsObserverRunning )&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CoreServices.SpatialAwarenessSystem.SuspendObservers();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CoreServices.SpatialAwarenessSystem.ClearObservations();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; else&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CoreServices.SpatialAwarenessSystem.ResumeObservers();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br&gt;}&lt;/pre&gt;&lt;p&gt;Note that “ClearObservations” is necessary, as merely calling Suspend only stops the &lt;em&gt;updating&lt;/em&gt; of the Spatial Map – the graphic representation still stays active. This was actually added after feedback from yours truly ;)&lt;/p&gt;&lt;p&gt;As to checking whether or not the observer is / observers are actually running I have devised this little trick&lt;/p&gt;&lt;pre&gt;private bool IsObserverRunning&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; var providers = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ((IMixedRealityDataProviderAccess)CoreServices.SpatialAwarenessSystem)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .GetDataProviders&amp;lt;IMixedRealitySpatialAwarenessObserver&amp;gt;();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return providers.FirstOrDefault()?.IsRunning == true;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br&gt;}&lt;/pre&gt;&lt;p&gt;&lt;img width=&quot;100&quot; height=&quot;99&quot; align=&quot;right&quot; style=&quot;border: 0px currentcolor; border-image: none; float: right; display: inline; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190813/Button.png&quot; border=&quot;0&quot;&gt;I check if there’s an observer and assume that if the first one is running, so is probably the rest. Although in practice, on a HoloLens, there will be only one observer running anyway.&lt;/p&gt;&lt;p&gt;You can activate and de-activate the Spatial Map by pressing the floating button, where &lt;a href=&quot;https://github.com/LocalJoost/SpatialMapInteraction/blob/master/Assets/App/Scripts/SpatialMapToggler.cs&quot; target=&quot;_blank&quot;&gt;the SpatialMapToggler&lt;/a&gt; behaviour is attached to.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;If you run and deploy the &lt;a href=&quot;https://github.com/LocalJoost/SpatialMapInteraction&quot; target=&quot;_blank&quot;&gt;demo project&lt;/a&gt; you will find a button floating before you (in the direction that you looked when the app started) that you can use to toggle the Spatial Map, and to the right a little cube. In addition, a text floating in your vision instructs you either to look at the spatial map or air tap when you actually do – and then a cylinder will appear. Like this in this little video:&lt;/p&gt;&lt;iframe width=&quot;650&quot; height=&quot;365&quot; src=&quot;https://www.youtube.com/embed/GpPNK1AHrBg&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot;&gt;&lt;/iframe&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/08/migrating-to-mrtk2interacting-with.html</link><author>noreply@blogger.com (Joost van Schaik)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/GpPNK1AHrBg/default.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-2849863121185056432</guid><pubDate>Tue, 30 Jul 2019 13:55:00 +0000</pubDate><atom:updated>2019-07-30T20:32:24.520+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">IL2CPP</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Visual Studio 2019</category><title>Fixing error Failed to locate “CL.exe” or MSB8020 when deploying IL2CPP solution</title><description>&lt;h2&gt;Symptom&lt;/h2&gt;&lt;p&gt;You have created a Unity project to create an app using MRTK2, and you want to use the new IL2CPP backend. You open the solution in Visual Studio 2019, you try to deploy it by using Build/Deploy and all the way at the end the compiler complains about “CL.exe” missing.&lt;/p&gt;&lt;p&gt;Alternatively, you might get the slightly more verbose error:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;error MSB8020: The build tools for Visual Studio 2017 (Platform Toolset = &#39;v141&#39;) cannot be found. To build using the v141 build tools, please install Visual Studio 2017 build tools.&amp;nbsp; Alternatively, you may upgrade to the current Visual Studio tools by selecting the Project menu or right-click the solution, and then selecting &quot;Retarget solution&quot;.&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;Cause&lt;/h2&gt;&lt;p&gt;You have most likely used the the recommended Unity version (2018.4.2f1) to create the project. This version – the name gives it away – was released before Visual Studio 2019, and therefore assumes the presence of Visual Studio &lt;em&gt;2017 &lt;/em&gt;and it’s accompanying C++ tools set, ‘V141’. So Unity generated a C++ solution referencing that tool set. &lt;/p&gt;&lt;p&gt;But now it’s 2019, you have kissed Visual Studio 2017 goodbye, installed Visual Studio 2019. And that comes with tool set V142. &lt;/p&gt;&lt;h2&gt;Solution&lt;/h2&gt;&lt;p&gt;Either you install V141 using the Visual Studio Installer, or you tell the generated solution to use V142. I personally prefer the last one, because newer is always better right ;)&lt;/p&gt;&lt;p&gt;Simply right-click the project in the solution that has “(Universal Windows)” behind it’s name, select properties, tab general and then the problem is already pretty evident:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;434&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://schaikweb.net/blog/20190730/Properties.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;Simply select Visual Studio 2019 (142) for Project Toolset and you are good to go. This setting will stay as long as you don’t delete the generated project – Unity will simply change what needs to be changed, and leave as much as it can (to speed up the generation process).&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Simple fix, but can be hard to find. Hence a simple blog about it&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/07/fixing-error-failed-to-locate-clexe-or.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-6162915229813565212</guid><pubDate>Mon, 29 Jul 2019 17:20:00 +0000</pubDate><atom:updated>2019-07-30T08:57:58.681+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Visual Studio</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Minimal required software for MRTK2 development for HoloLens 2 and Immersive headsets</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;A short one this time – and codeless to. You see, &lt;a href=&quot;https://www.meetup.com/Mixed-Reality-User-Group/events/262193756/&quot; target=&quot;_blank&quot;&gt;next Saturday I will be giving an workshop&lt;/a&gt; for &lt;a href=&quot;https://www.mixug.com/&quot; target=&quot;_blank&quot;&gt;MixUG&lt;/a&gt; Netherlands about development with the &lt;a href=&quot;https://github.com/microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit 2&lt;/a&gt; for Immersive headsets, together with my colleague, partner in crime and fellow MVP &lt;a href=&quot;https://www.mixug.com/&quot; target=&quot;_blank&quot;&gt;Alexander Meijers&lt;/a&gt;. One of the things that came up preparing for this workshop was what you would actually need to develop with the Mixed Reality Toolkit 2. Since ye olden days of the HoloToolkit, quite a few things have changed – Unity, the minimal OS version, and there’s even a new version of Visual Studio. So I set out to complete a minimal shopping list with a few optional items. Fortunately, our friends over at Microsoft &lt;a href=&quot;https://portal.azure.com&quot; target=&quot;_blank&quot;&gt;Azure&lt;/a&gt; make it quite simple to spin up a totally pristine machine so you don’t run into the typical developer machine issues – multiple versions of Visual Studio with different workloads and a myriad of Unity versions – which makes it hard to tell sometimes what is required for what app.&lt;/p&gt;&lt;h2&gt;OS version&lt;/h2&gt;&lt;p&gt;Easy one. Windows 10, 1809 or (recommended) 1903. Everything I tested, I tested on Windows 10 Pro&lt;/p&gt;&lt;h2&gt;Visual Studio&lt;/h2&gt;&lt;p&gt;You will need Visual Studio 2019 community edition. 2017 will work too, but is &lt;em&gt;much&lt;/em&gt; slower. &lt;a href=&quot;https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&amp;amp;rel=16&quot;&gt;Download Visual Studio 2019 community from this link&lt;/a&gt; and choose the following work loads:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;UWP development with optional components USB connectivity and C++ (V142) UWP tools checked&lt;/li&gt;&lt;li&gt;Game development with Unity with the optional component 2018.3 64-bit editor &lt;i&gt;unchecked&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In images:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;320&quot; src=&quot;https://schaikweb.net/blog/20190729/Workload1.png&quot;&gt;&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;321&quot; src=&quot;https://schaikweb.net/blog/20190729/Workload2.png&quot;&gt;&lt;/p&gt;&lt;p&gt;Make sure you install Visual Studio &lt;em&gt;before&lt;/em&gt; Unity.&lt;/p&gt;&lt;h2&gt;Offline installer&lt;/h2&gt;&lt;p&gt;A fun trick – if you want to make an offline installer for the community edition for these particular workloads, open a command prompt after downloading the installer, and type (on one line):&lt;/p&gt;&lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;vs_community.exe --layout c:\vsinstaller &lt;br&gt;--add Microsoft.VisualStudio.Workload.ManagedGame &lt;br&gt;--add Microsoft.VisualStudio.Workload.Universal &lt;br&gt;--add Microsoft.VisualStudio.Component.Windows10SDK.IpOverUsb &lt;br&gt;--add Microsoft.VisualStudio.ComponentGroup.UWP.VC --lang en-US&lt;/font&gt;&lt;/p&gt;&lt;p&gt;In c:\vsinstaller you will then find a complete install ‘layout’ for all the necessary components. Might be useful if you want to prepare multiple computers.&lt;/p&gt;&lt;h2&gt;Unity &lt;/h2&gt;&lt;p&gt;2018.4.2f1, taken from &lt;a href=&quot;https://github.com/microsoft/MixedRealityToolkit-Unity/blob/mrtk_development/ProjectSettings/ProjectVersion.txt&quot;&gt;ProjectSettings/ProjectVersion.txt in the mrtk_development branch&lt;/a&gt;. This particular version can be downloaded directly &lt;a href=&quot;https://unity3d.com/get-unity/download?thank-you=update&amp;amp;download_nid=62614&amp;amp;os=Win&quot;&gt;from this link&lt;/a&gt;.&lt;p&gt;Choose as minimal components &lt;ul&gt;&lt;li&gt;Unity 2018.4.2f1&lt;/li&gt;&lt;li&gt;UWP Build Support&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;img width=&quot;400&quot; height=&quot;311&quot; src=&quot;https://schaikweb.net/blog/20190729/Unity.PNG&quot;&gt;&lt;/p&gt;&lt;p&gt;Mind you – this sets you op for HoloLens 2 and Windows Mixed Reality Immersive headsets &lt;em&gt;only. &lt;/em&gt;&lt;/p&gt;&lt;h2&gt;Optional – HoloLens 2 emulator&lt;/h2&gt;&lt;p&gt;I have already &lt;a href=&quot;https://dotnetbyexample.blogspot.com/2019/04/first-look-at-hololens-2-emulator.html&quot;&gt;written extensively about it&lt;/a&gt;. You can get it &lt;a href=&quot;https://go.microsoft.com/fwlink/?linkid=2098508&quot;&gt;here&lt;/a&gt;. Be aware that it requires Hyper-V being installed. If you have installed Windows 10 1903, it will run right away. &lt;a href=&quot;https://forum.unity.com/threads/cannot-run-hololens2-emulator.696779/#post-4664951&quot; target=&quot;_blank&quot;&gt;On 1809 you will need some trickery&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;It’s not that hard to get up and running for MRTK2 development for HoloLens 2 and Windows Mixed Reality Immersive headsets. And now you have a nice complete ‘shopping list’ for when you want to prepare your PC.&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/07/minimal-required-software-for-mrtk2.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-366972282965864754</guid><pubDate>Sun, 14 Jul 2019 18:39:00 +0000</pubDate><atom:updated>2019-07-15T18:40:16.852+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRTK2–manipulating holograms by grabbing</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;To be honest, the title of this blog post is a bit weird, because in &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;&lt;a href=&quot;https://github.com/Microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit&lt;/a&gt; 1 the concept of grabbing was unknown, as HoloLens 1 does not support this kind of gestures. But nevertheless, as I am on this quest of documenting all the gems I discover while migrating an existing app to &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;Mixed Reality&lt;/a&gt; Toolkit 2, this is one of the things I came across so I am shoehorning it in this blog post series – the 8th installment of it already. And the fun thing about this one if that although there is a &lt;a href=&quot;https://github.com/LocalJoost/MRTK2GrabDemo&quot;&gt;demo project&lt;/a&gt; available, I am going to write no code at all. The whole concept of manipulation by grabbing can be done by simply dragging MRTK2 components on top of a game object.&lt;/p&gt;&lt;h2&gt;&#39;Far manipulation&#39;&lt;/h2&gt;&lt;p&gt;This is really extremely simple. If I want to make a cube draggable in the &#39;classic&#39; sense - that is, point a cursor to it, pinch and move my hand, and then the cube follows, all you have to do is add a ManipulationHandler to the cube, with default settings:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190715/manipultationhandler.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And then you simply point the &#39;hand ray&#39; to it, pinch and move:&lt;/p&gt;&lt;iframe width=&quot;650&quot; height=&quot;365&quot; src=&quot;https://www.youtube.com/embed/0x8370Lc97A&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot;&gt;&lt;/iframe&gt;&lt;p&gt;But as you could see, I can only drag it. I can&#39;t move it anymore - or rotate - as my hand comes closer, like at the end of the movie. I fact, I can&#39;t do anything anymore.&lt;/p&gt;&lt;h2&gt;Allow grabbing and moving&lt;/h2&gt;&lt;p&gt;For that, we will need to add another script: Near Interaction Grabbable. &lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190715/nearinteractiongrabbable.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And now, if the hand comes close to the cube, you can do all kinds of crazy stuff with it&lt;/p&gt;&lt;iframe width=&quot;650&quot; height=&quot;365&quot; src=&quot;https://www.youtube.com/embed/7ySYBGkQWVs&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot;&gt;&lt;/iframe&gt;&lt;h2&gt;Some settings to consider&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;If you don&#39;t want to allow &#39;far manipulation&#39; (the first type) but only want to allow the manipulation by grapping, you can uncheck the &quot;Allow Far Manipulation&quot; on the ManipulationHandler.&lt;/li&gt;&lt;li&gt;If you want to see where the actual grab connection point is, check the &quot;Show Tether When Manipulating&quot; checkbox on Near Interaction Grabbable. This will look like this:&lt;/li&gt;&lt;/ul&gt;&lt;iframe width=&quot;560&quot; height=&quot;365&quot; src=&quot;https://www.youtube.com/embed/OaneY_vuBqk&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot;&gt;&lt;/iframe&gt;&lt;p&gt;I bet there are more settings to consider, but I haven&#39;t tried those yet (or felt the need to do so).&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;The code of this completely code-less sample &lt;a href=&quot;https://github.com/LocalJoost/MRTK2GrabDemo&quot; target=&quot;_blank&quot;&gt;can be found here&lt;/a&gt;. I can&#39;t wait to add code like this to real-world &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 2 projects. But alas, we still need to wait for the device :)&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/07/migrating-to-mrtk2manipulating.html</link><author>noreply@blogger.com (Joost van Schaik)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/0x8370Lc97A/default.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-908061367877703036</guid><pubDate>Tue, 09 Jul 2019 07:30:00 +0000</pubDate><atom:updated>2019-07-10T10:58:51.932+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRTK2– handling tap, touch and focus ‘manually’ (in code)</title><description>&lt;h2&gt;Wait a minute – you did handle tap before, right?&lt;/h2&gt;&lt;p&gt;Indeed, dear reader, I did. But I also had signed up for a &lt;a href=&quot;https://www.mixug.com/&quot; target=&quot;_blank&quot;&gt;MixUG&lt;/a&gt; &lt;a href=&quot;https://twitter.com/mixugnl/status/1146464058733998080&quot; target=&quot;_blank&quot;&gt;session on Wednesday July 3&lt;/a&gt;. And while making demos for that I learned some other ways to handle interaction. Once again it shows that the best way to learn things is to try to teach them – because the need to explain things induces the need to actually obtain a deeper knowledge.&lt;/p&gt;&lt;h2&gt;Ye olde way&lt;/h2&gt;&lt;p&gt;In the MRTK 1, it was thus:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Handle tap – implement IInputClickHandler&lt;/li&gt;&lt;li&gt;Handle drag – implement IManipulationHandler&lt;/li&gt;&lt;li&gt;Handle focus – implement &lt;br&gt;&lt;/li&gt;&lt;li&gt;Handle touch – forget it. ;)&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;The new way &lt;/h2&gt;&lt;p&gt;In the MRTK 2 it is now&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Handle tap – implement IMixedRealityPointerHandler&lt;/li&gt;&lt;li&gt;Handle drag – see above&lt;/li&gt;&lt;li&gt;Handle focus – implement IMixedRealityFocusHandler&lt;/li&gt;&lt;li&gt;Handle touch – IMixedRealityTouchHandler&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Now I am going to ignore drag for this tutorial, and concentrate me on tap, focus and touch&lt;/p&gt;&lt;h2&gt;IMixedRealityPointerHandler&lt;/h2&gt;&lt;p&gt;This requires you to implement four methods:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;OnPointerDown&lt;/li&gt;&lt;li&gt;OnPointerDragged&lt;/li&gt;&lt;li&gt;OnPointerUp&lt;/li&gt;&lt;li&gt;OnPointerClicked&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;OnPointerClicked basically intercept a tap or an air tap, and will work as such as you deploy the &lt;a href=&quot;https://github.com/LocalJoost/ClickTouchFocus&quot; target=&quot;_blank&quot;&gt;demo project&lt;/a&gt; to a &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 1. After being bitten by people tapping just a tiny bit to slow and therefore not getting response, I tend to implement OnPointerDown rather than OnPointerClicked to capture a &#39;tap&#39; event, but that&#39;s a matter of preference.&lt;/p&gt;&lt;h2&gt;IMixedRealityFocusHandler&lt;/h2&gt;&lt;p&gt;You will need to implement:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;OnFocusEnter&lt;/li&gt;&lt;li&gt;OnFocusExit&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The method names are the same as in MRKT1, only the signatures not - you now get a parameter of type FocusEventData which does give you some more information - by what the object was focused (we have multiple ways of doing that now!), what the previous focused object was, and what the new focused object is.&lt;/p&gt;&lt;h2&gt;IMixedRealityTouchHandler&lt;/h2&gt;&lt;p&gt;This requires you to implement &lt;/p&gt;&lt;ul&gt;&lt;li&gt;OnTouchStarted&lt;/li&gt;&lt;li&gt;OnTouchCompleted&lt;/li&gt;&lt;li&gt;OnTouchUpdated&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;But there is a twist to that. As we will soon see.&lt;/p&gt;&lt;h2&gt;Show-off&lt;/h2&gt;&lt;p&gt;To show off how it all works, I have created &lt;a href=&quot;https://github.com/LocalJoost/ClickTouchFocus&quot; target=&quot;_blank&quot;&gt;a little demo project&lt;/a&gt;. You can run it either in the emulator or the Unity editor (or a HoloLens 2, if you are in the HoloLens team and part some very few selected parties - I am unfortunately neither).&lt;/p&gt;&lt;p&gt;I have created a little script &lt;a href=&quot;https://github.com/LocalJoost/ClickTouchFocus/blob/master/Assets/App/Scripts/CodedInteractionResponder.cs&quot; target=&quot;_blank&quot;&gt;CodedInteractionResponder&lt;/a&gt; that shows off how this works. This script implements all the three interfaces I just wrote about. If you open the demo project in Unity show itself like this. All three cubes have the script attached to them.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;260&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190710/Demo1.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;The text above the cubes will show how much times a cube has been either focused, touched or clicked. I you press play and then the space bar, the right hand will appear (or use ctrl for the left hand). Moving the hand can be done by using the mouse - if you move the hand ray over the cubes it will trigger a focus event, if you tap the left mouse button you will trigger a tap, and if you move the hand towards the cube (using the WASD keys) it will trigger a touch event.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;331&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190710/Demo2.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;That is to say - you would expect that. But that is not always the case&lt;/p&gt;&lt;p&gt;What happens is this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;You can click or focus the green cube, but you cannot touch it. Nothing happens if you try.&lt;/li&gt;&lt;li&gt;&lt;div align=&quot;left&quot;&gt;You can click, focus or touch the red cube, but if you touch it, the number of times it&#39;s &lt;em&gt;clicked&lt;/em&gt; is increasing. Not the number of touches.&lt;/div&gt;&lt;/li&gt;&lt;li&gt;Only the blue cube works as expected.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Yet they all have the CodedInteractionResponder. How does this compute?&lt;/p&gt;&lt;h2&gt;NearInteractionTouchable&lt;/h2&gt;&lt;p&gt;The best way to explain this, is an image showing the bottom half of all the three cubes&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;225&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190710/threecubes.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;The green cube misses the NearInteractionTouchable. This script is necessary to have touch events being fired at all. So unlike IMixedRealityPointerHandler and IMixedRealityFocusHandler, where a mere implementation of the interface will trigger an event, a &lt;em&gt;touch&lt;/em&gt; event - that is, methods in IMixedRealityTouchHandler being called - &lt;em&gt;requires the addition of a NearInteractionTouchable script&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;And NearInteractionTouchable has another trick up it&#39;s sleeve. Suppose you have a button - whether it&#39;s (air) tapped or actually touched/pressed, you want to activate the same code. If you change &quot;Events to Receive&quot; from it&#39;s default &quot;Touch&quot; to &quot;Pointer&quot; (as I did with the red cube) touching the cube will actually trigger a pointer event. This saves you a few lines of code. So basically NearInteractionTouchable can act as a kind of event router. And this is why the red cube never shows a touch event - but a click event instead.&lt;/p&gt;&lt;p&gt;Be aware NearInteractionTouchable&lt;em&gt; needs a collider to work on&lt;/em&gt;. This collider needs to be on the same object the script is on. So if you make an empty game object as a hat stand for a a bunch of smaller game objects, make sure to manually add a collider that envelops all game objects, otherwise the &#39;touch&#39; won&#39;t seem to work.&lt;/p&gt;&lt;h2&gt;What, no code?&lt;/h2&gt;&lt;p&gt;Yes, there is code, but it&#39;s pretty straightforward and if you want to have a look at &lt;a href=&quot;https://github.com/LocalJoost/ClickTouchFocus/blob/master/Assets/App/Scripts/CodedInteractionResponder.cs&quot; target=&quot;_blank&quot;&gt;CodeInteractionResponder&lt;/a&gt;, have a look in GitHub. It&#39;s actually so simple I felt it a little bit overdone to verbatim repeat parts in this blog post itself.&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/07/migrating-to-mrtk2-handling-tap-touch.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-5007779519633793000</guid><pubDate>Wed, 19 Jun 2019 15:03:00 +0000</pubDate><atom:updated>2019-06-19T17:04:30.476+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRTK2 - missing Singleton and 3DTextPrefab</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;If you are migrating from the HoloToolkit to &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;&lt;a href=&quot;https://github.com/Microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit&lt;/a&gt; 2&lt;/a&gt; &#39;cold turkey&#39;, as I am doing for my &lt;a href=&quot;https://www.microsoft.com/store/productId/9P6SVQQCP2SQ&quot; target=&quot;_blank&quot;&gt;AMS HoloATC&lt;/a&gt; app, a lot of things break, as I already &lt;a href=&quot;http://dotnetbyexample.blogspot.com/2019/05/migrate-to-mrtk2-mixed-reality-toolkit.html&quot; target=&quot;_blank&quot;&gt;said in the first post of this series&lt;/a&gt;. For things that you can tap, you can simply change the implementing interface from IInputClickHandler or IManipulationHandler to a couple of other interface and change the signature a bit - that&#39;s not complex, only tedious, depending on how much you have used it.&lt;/p&gt;&lt;p&gt;What I found really hard was the removal of the Singleton class and the 3DTextFab. I used both quite extensively. The first one I needed for like data access classes as the concept of services that was introduced in the &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;&lt;a href=&quot;https://github.com/Microsoft/MixedRealityToolkit-Unity/blob/mrtk_release/Documentation/GettingStartedWithTheMRTK.md&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit 2&lt;/a&gt;&amp;nbsp;&lt;/a&gt; was not yet available, and the other... well basically &lt;em&gt;all &lt;/em&gt;my texts were 3DTextPrefabs so any kind of user feedback in text format was gone. Because so much breaks at the same time, it&#39;s very hard to step by step rebuilding your app to a working condition. Basically you have to change &lt;em&gt;everything&lt;/em&gt; before something starts to work again. Since I was still learning by doing, there was no way to test if I was doing things more or less right. I got stuck, and took a radical approach.&lt;/p&gt;&lt;h2&gt;Introducing HoloToolkitCompatiblityPack&lt;/h2&gt;&lt;p&gt;&lt;img width=&quot;364&quot; height=&quot;288&quot; align=&quot;left&quot; style=&quot;margin: 0px 9px 0px 0px; border: 0px currentcolor; border-image: none; float: left; display: inline; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190619/HTKCP.png&quot; border=&quot;0&quot;&gt;I have created a little Unity Package that contains the things that made it hard for &lt;em&gt;me&lt;/em&gt; to get a step-by-step migration to the MRTK2 and christened it the HoloToolkitCompatiblityPack. It contains minimal amount of scripts &lt;em&gt;and meta files&lt;/em&gt; to have Singleton and 3DTextFab working inside an MRTK2 built app. As I will be migrating more apps, I will probably update the package with other classes that I need. You can find the package file &lt;a href=&quot;https://github.com/LocalJoost/HoloToolkitCompatiblityPack/raw/master/UnityPackage/HoloToolkitCompatiblityPack.unitypackage&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt; and the project &lt;a href=&quot;https://github.com/LocalJoost/HoloToolkitCompatiblityPack&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;. If you take your existing HoloToolkit based app, yank out the HoloToolkit, replace it by the MRTK2, then import the HoloToolkitCompatiblityPack package, you at least have to fix a few less things to at least get your app to a minimal state of function again.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;h2&gt;Caveat emptor&lt;/h2&gt;&lt;p&gt;Yes, of course you can use the HoloToolkitCompatiblityPack in your production app, and ship a kind of Frankenbuild using both MRTK2 and this. Do let yourself be tempted to do that. See this package as a kind of scaffolding, or a temporary beam to hold up the roof while you are replacing a bearing wall. For 3DTextFab I tend to turn a blind eye, but please don&#39;t use Singleton again. Convert those classes into services one by one. Then remove the Singleton from the HoloToolkitCompatiblityPack to make sure everything works without. This is for migration purposes &lt;em&gt;only.&lt;/em&gt; &lt;/p&gt;&lt;p&gt;Take the high road, not the low technical debt road. &lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Making this package helped me forward with the migration quite a lot. I hope it helps other too. I&#39;d love to hear some feedback on this.&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/06/migrating-to-mrtk2-missing-singleton.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-5295746446529817470.post-6656709867011580476</guid><pubDate>Wed, 29 May 2019 11:30:00 +0000</pubDate><atom:updated>2019-05-29T13:30:37.301+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">HoloLens</category><category domain="http://www.blogger.com/atom/ns#">HoloLens2</category><category domain="http://www.blogger.com/atom/ns#">MRTK2</category><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">Windows Mixed Reality</category><title>Migrating to MRTK2 - looking a bit closer at tapping, and trapping &#39;duplicate&#39; events</title><description>&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;In my &lt;a href=&quot;https://dotnetbyexample.blogspot.com/2019/05/migrating-to-mrtk2-iinputclickhandler.html&quot; target=&quot;_blank&quot;&gt;previous post&lt;/a&gt; I wrote about how game objects can be made clickable (or &#39;tappable&#39;) using the &lt;a href=&quot;https://www.microsoft.com/en-us/windows/windows-mixed-reality&quot; target=&quot;_blank&quot;&gt;&lt;a href=&quot;https://github.com/Microsoft/MixedRealityToolkit-Unity&quot; target=&quot;_blank&quot;&gt;Mixed Reality Toolkit&lt;/a&gt; 2, and how things changed from MRTK1. And in fact, when you deploy the app to a HoloLens 1, my demo actually works as intended. But then I noticed something odd in the editor, and made a variant of the app that went with the previous blog post to see how things work- or might work- in &lt;a href=&quot;https://www.microsoft.com/en-us/hololens&quot; target=&quot;_blank&quot;&gt;HoloLens&lt;/a&gt; 2.&lt;/p&gt;&lt;h2&gt;Debugging ClickyThingy ye olde way&lt;/h2&gt;&lt;p&gt;Like I wrote before, it&#39;s possible to &lt;a href=&quot;https://dotnetbyexample.blogspot.com/2019/03/debugging-c-code-with-unity-il2cpp.html&quot; target=&quot;_blank&quot;&gt;debug the C# code of a running IL2CPP C++ app running on a HoloLens&lt;/a&gt;. To debug using breakpoints is a bit tricky when you are dealing with rapidly firing event - stopping through the debugger might actually have some influence on the order events play out. So I resorted to the good old &quot;Console.WriteLine-style&quot; of debugging, and added a floating text in the app that shows what&#39;s going on.&lt;/p&gt;&lt;p&gt;The ClickableThingy behaviour I made in the previous post then looks like this:&lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;using Microsoft.MixedReality.Toolkit.Input;
using System;
using TMPro;
using UnityEngine;

public class ClickableThingyGlobal : BaseInputHandler, IMixedRealityInputHandler
{
    [SerializeField]
    private TextMeshPro _debugText;

    public void OnInputUp(InputEventData eventData)
    {
        GetComponent&amp;lt;MeshRenderer&amp;gt;().material.color = Color.white;
        AddDebugText(&quot;up&quot;, eventData);
    }

    public void OnInputDown(InputEventData eventData)
    {
        GetComponent&amp;lt;MeshRenderer&amp;gt;().material.color = Color.red;
        AddDebugText(&quot;down&quot;, eventData);
    }

    private void AddDebugText( string eventPrefix, InputEventData eventData)
    {
        if( _debugText == null)
        {
            return;
        }
        var description = eventData.MixedRealityInputAction.Description;
        _debugText.text += 
            $&quot;{eventPrefix} {gameObject.name} : {description}{Environment.NewLine}&quot;;
    }
}



&lt;/pre&gt;&lt;p&gt;Now in the HoloLens 1, things are exactly like you expect. Air tapping the sphere activates Up and Down events exactly once for every tap (because the Cube gets every tap, even when you don&#39;t gaze at it - see my &lt;a href=&quot;https://dotnetbyexample.blogspot.com/2019/05/migrating-to-mrtk2-iinputclickhandler.html&quot; target=&quot;_blank&quot;&gt;previous post&lt;/a&gt; for an explanation)&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;546&quot; height=&quot;435&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190529/ClickOnHoloLens.jpg&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;When you run the same code in the editor, though, you get a different result:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;546&quot; height=&quot;456&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190529/ClickInEditor.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;Tap versus Grip - and CustomPropertyDrawers&lt;/h2&gt;&lt;p&gt;The interesting thing is, when you &#39;air tap&#39; in the editor (using the space bar and the left mouse button), thumb and index finger of the simulated hand come together. This, now, is recognized as a tap followed by a grip, apparently. &lt;/p&gt;&lt;p&gt;&lt;img width=&quot;546&quot; height=&quot;362&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190529/ClickinEditor2.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;So we need to filter the events coming in through OnInputUp and OnInputDown to respond to the actual events we want. This is where things get a little bit unusual - there is no enumeration of sorts that you can use to compare you actual event against. The available events are all in the configuration, so they are dynamically created. &lt;/p&gt;&lt;p&gt;The way to do some actual filtering is to add a property of type MixedRealityInputAction to your behaviour (I used _desiredInputAction). Then the MRTK2 automatically creates a drop down with possible to events to select from:&lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190529/DropdownInputAction.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;How does this magic work? Well, the MRTK2 contains a &lt;a href=&quot;https://docs.unity3d.com/ScriptReference/CustomPropertyDrawer.html&quot; target=&quot;_blank&quot;&gt;CustomPropertyDrawer&lt;/a&gt; called &lt;a href=&quot;https://github.com/microsoft/MixedRealityToolkit-Unity/blob/9ab0e9b69afe45f7856b5731436ae427c3fe7cc4/Assets/MixedRealityToolkit/Inspectors/PropertyDrawers/InputActionPropertyDrawer.cs&quot; target=&quot;_blank&quot;&gt;InputActionPropertyDrawer&lt;/a&gt; that automatically creates this drop down whenever you add a property of type MixedRealityInputAction to your behaviour. The values in this list are pulled from the configuration. This fits with the idea of the MRTK2 that everything must be configurable ad infinitum. Which is cool but sometimes it makes things confusing.&lt;/p&gt;&lt;p&gt;Anyway, you select the event you want to test for in the UI, in this case &quot;Select&quot;:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;546&quot; height=&quot;100&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190529/Selected.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;And then you have to check if the event methods if the event matches your desired event. &lt;/p&gt;&lt;pre style=&quot;font-size: 12px;&quot;&gt;if (eventData.MixedRealityInputAction != _desiredInputAction)
{
    return;
}&lt;/pre&gt;&lt;p&gt;And then everything works as you expect: only the select event results in an action by the app.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;546&quot; height=&quot;414&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190529/ClickInEditor3.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;h2&gt;How about HoloLens 2?&lt;/h2&gt;&lt;p&gt;I could only test this in the emulator. The odd things is, even without the check on the input action, only the select action was fired, even when I pinched the hand using the control pane:&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;640&quot; height=&quot;418&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://www.schaikweb.net/blog/20190529/ClickInHL2emulator.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;So I have no idea if this is actually necessary on a real live HoloLens 2, but my friends and fellow MVPs &lt;a href=&quot;https://twitter.com/stephenhodgson_&quot; target=&quot;_blank&quot;&gt;Stephen Hodgson&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/SimonDarksideJ&quot; target=&quot;_blank&quot;&gt;Simon &#39;Darkside&#39; Jackson&lt;/a&gt; have both mentioned this kind of event type check as being necessary in a few on line conversations (although I then did not understand why). So I suppose it is :)&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Common wisdom has it that the best thing about teaching is that you learn a lot &lt;em&gt;yourself&lt;/em&gt;. This post is excellent proof of that wisdom. If you think this here old MVP is the end-all and know-all of this kind of stuff, think again. I knew of customer editors, but I literally just learned the concept of CustomPropertyDrawer while I was writing this post. I had no idea it existed, but I found it&amp;nbsp; because I wanted to know how the heck the editor got all the possible MixedRealityInputAction from the configuration and show that in such a neat list. Took me quite some searching, actually - which is logical, if you don&#39;t know what exactly you are looking for ;).&lt;/p&gt;&lt;p&gt;I hope this benefits you as well. &lt;a href=&quot;https://github.com/LocalJoost/MRTK2Tap/tree/TapCloseLook&quot; target=&quot;_blank&quot;&gt;Demo project here&lt;/a&gt; (branch TapCloseLook).&lt;/p&gt;</description><link>http://dotnetbyexample.blogspot.com/2019/05/migrating-to-mrtk2-looking-bit-closer.html</link><author>noreply@blogger.com (Joost van Schaik)</author><thr:total>2</thr:total></item></channel></rss>