Jekyll2023-08-31T08:32:50+00:00http://boxler.me/feed.xmlBea BoxlerI'm Bea, I'm a developer and this website is mostly my thoughts on things and things that I have worked out or have tried and might have worked. Maybe some things don't work. I'm not responsible for anything I write. (And nor are my university or employers) C2C3 E547 0391 2E36 42F0 ADED 43BF 0FED 54D6 04EBGetting started with PGP and GPG2019-01-28T00:00:00+00:002019-01-28T00:00:00+00:00http://boxler.me/security/2019/01/28/Using-PGP-GPG<h1 id="getting-started-with-pgp-and-gpg">Getting Started with PGP and GPG</h1>
<p>PGP is the best way of getting started with digital security, as it is the most well used and espablished form of encryption. It is often used by journalists and whistleblowers to communicate securely. It is based around a network of trust where Bob creates a key, and becasue Alice knows him in person, and so does Robby, and because Sarah trusts Alice, she trusts that the key signed by her does really belong to Bob.</p>
<p>We are going to be using GPG2 in this guide, which is an open source implementation of the PGP standard.</p>
<h2 id="getting-started">Getting started</h2>
<p>Getting started with PGP is easy. First things first though, we need to get everything installed.</p>
<p>First, install GPG:</p>
<ul>
<li>
<p>On Windows, you should install <a href="https://www.gpg4win.org">GPG4Win</a> which installs everything you need and more. It also includes Kleopatra, however I won’t be going into how to use that (primarily because I don’t really like it). You will want to use GPG through the Powershell for this guide.</p>
<p>If things don’t work well for you on Windows (I have had so many issues getting all of this working on Windows) try contacting me on Twitter <a href="https://twitter.com/benjaminboxler">@benjaminboxler</a> or google it.</p>
</li>
<li>
<p>On MacOS, your best option is to install <a href="https://gpgtools.org">GPGTools</a> which includes lots of useful things, including a Mail plugin, a solid Pinentry tool and a nice keychain tool. However for the purposes of this tutorial, we are just going to use the terminal for everything.</p>
</li>
<li>
<p>On Debian and Ubuntu, it is availale in the universe repository by default:</p>
<p><code class="language-plaintext highlighter-rouge">sudo apt install gpg2</code></p>
<p>It’s important to install the GPG2 package, as just install the gpg package will install GPG 1.4 which is no longer secure and is missing some important features we will be using later.</p>
</li>
</ul>
<p>You might need to restart or open your terminal or shell session before continuing.</p>
<p>Next you need to generate your first set of encryption keys using <code class="language-plaintext highlighter-rouge">gpg --generate-key</code>, where you will be prompted for your real name, then your email address you want associated with your key. You’ll be able to add another email address later. It will ask you to confirm your details - if they are all OK type <code class="language-plaintext highlighter-rouge">O</code>. You’ll want to move your mouse around and use your computer whilst the key generates. You should see output like this when it is complete:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gpg --generate-key
gpg (GnuPG) 2.2.1; Copyright (C) 2017 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Note: Use "gpg --full-generate-key" for a full featured key generation dialog.
GnuPG needs to construct a user ID to identify your key.
Real name: John Doe
Email address: john@example.com
You selected this USER-ID:
"John Doe <john@example.com>"
Change (N)ame, (E)mail, or (O)kay/(Q)uit? o
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key 831F8A116F2624AF marked as ultimately trusted
gpg: revocation certificate stored as '/Users/benboxler/.gnupg/openpgp-revocs.d/912212B2842647CD6966A0C3831F8A116F2624AF.rev'
public and secret key created and signed.
pub rsa2048 2019-01-28 [SC] [expires: 2021-01-27]
912212B2842647CD6966A0C3831F8A116F2624AF
uid John Doe <john@example.com>
sub rsa2048 2019-01-28 [E] [expires: 2021-01-27]
</code></pre></div></div>
<p>Now you have your first set of keys!</p>
<h2 id="adding-a-subkey">Adding a subkey</h2>
<p>To edit the key, you will need to run <code class="language-plaintext highlighter-rouge">gpg --edit-key <KeyID></code> where <code class="language-plaintext highlighter-rouge"><KeyId></code> is the ID of the key you just generated. In the above example the ID is <code class="language-plaintext highlighter-rouge">831F8A116F2624AF</code>. You can bring the ID back up by calling <code class="language-plaintext highlighter-rouge">gpg -K</code>. In this example I would call <code class="language-plaintext highlighter-rouge">gpg --edit-key 831F8A116F2624AF</code>. This should display your key details and provide you a list of options. The ones we care about are <code class="language-plaintext highlighter-rouge">adduid</code> and <code class="language-plaintext highlighter-rouge">addkey</code>. If you have a second email address, use <code class="language-plaintext highlighter-rouge">adduid</code> and then follow the prompts to add another email address. What we now need to do is add another signing subkey so we can move the primary key off the computer and somewhere more secure.</p>
<p>Calling <code class="language-plaintext highlighter-rouge">addkey</code> will ask you to choose what the subkey is for, and here we will want to type <code class="language-plaintext highlighter-rouge">4</code> for RSA: Sign Only. This will generate a subkey. Type <code class="language-plaintext highlighter-rouge">save</code> to save all the changes and exit the editor.</p>
<h2 id="uploading-to-a-keyserver">Uploading to a keyserver</h2>
<p>What we need to do now is upload your public key to a keyserver. Most of the public servers all mirror the data between themselves, so if you upload to a keyserver, within 24 hours it will be available on all the other servers. This means that no matter what server you use, people will be able to find and contact you on it.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --send-key <KeyID>
</code></pre></div></div>
<p>You should receive some output like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg: sending key 43BF0FED54D604EB to hkps://hkps.pool.sks-keyservers.net
</code></pre></div></div>
<p>and you will know your key is being distributed online correctly.</p>
<p>For added security, I keep a txt document on my website from a signed commit on my website easily accessible here: <a href="https://boxler.me/54D604EB.txt">https://boxler.me/54D604EB.txt</a></p>
<h2 id="generating-a-revocation-certificate">Generating a revocation certificate</h2>
<p>YOu need to generate a revocation certificate and keep it somewhere safe, you will need it if your master secret key gets compromised and you can upload your revocaition certificate to a keyserver and it will instruct users that it is no longer secure.</p>
<p><code class="language-plaintext highlighter-rouge">gpg --generate-revocation -a <KeyId> > <KeyId>.revocation.asc</code></p>
<p>Now you have to keep this one very safe - anyone who gets hold of this certificate can use it to revoke your certificate, making your certificates invalid. I reccomend putting it on an encrypted thumb drive somewhere safe.</p>
<p>To use it all you have to do is import it to your keychain and uplaod it to the keyserver.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --import <KeyId>.revocation.asc
gpg --send-key <KeyId>
</code></pre></div></div>
<h2 id="creating-your-everyday-keychain">Creating your everyday keychain</h2>
<p>Now that you have created your subkeys, you will need to export them. Once we have done this you can move these subkeys onto any other devices that you want to be able to use to encrypt or sign documents with.</p>
<p>You can export it by using <code class="language-plaintext highlighter-rouge">gpg --export-secret-subkeys <KeyID> > <KeyID>.subkeys.asc</code>
and then using something like <code class="language-plaintext highlighter-rouge">scp</code> or a memory stick to move it onto another computer or device. You probably shouldn’t email that file, either.</p>
<p>You will then be able to import it on another device with GPG installed using <code class="language-plaintext highlighter-rouge">gpg --import <KeyID>.subkeys.asc</code>, replacing keyID with your KeyID and ensuring your shell is in the same directory as the file.</p>
<h2 id="moving-your-master-key-somewhere-safer">Moving your master key somewhere safer.</h2>
<p>It’s not very safe to be leaving your master key on your computer, especially if you are using a laptop. Your master key is required to sign other people’s keys, or to create new subkeys for yourself. If your master key is compromised, you will need to revoke that entire identity key, burning it down and starting from scratch (which is really inconvenient).</p>
<p>The first thing to do is export your secret key. Once your export it you will need to move it somewhere safe. I’ll leave that up to you. I have one copy of my master key I keep at home on a memory stick, and another I have on an encrypted memory stick with someone that I trust.</p>
<p>To export it all we have to do is run</p>
<p><code class="language-plaintext highlighter-rouge">gpg -a --export-secret-key <KeyID> > <KeyID>.master.asc</code></p>
<p>again where <code class="language-plaintext highlighter-rouge"><KeyID></code> is the ID of your key that we found earlier. You can open this file in any text editor and you should see a file that looks somwhat like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-----BEGIN PGP PRIVATE KEY BLOCK-----
// lots of random characters...
-----END PGP PRIVATE KEY BLOCK-----
</code></pre></div></div>
<p>Now we can remove the master key from your system <strong><em>BUT ONLY IF YOU ARE CERTAIN THAT YOU HAVE BACKED IT UP</em></strong>. If you lose your master key you will be unable to revoke your subkeys, generate new subkeys, sign other people’s keys.</p>
<p>Deleting the master key can be achieved with <code class="language-plaintext highlighter-rouge">gpg --delete-secret-key <KeyID></code>. Once this is deleted, you can reimport the subkeys that you exported earlier, again with <code class="language-plaintext highlighter-rouge">gpg --import <KeyID>.subkey.asc</code>. Ensure your keys are there by typing <code class="language-plaintext highlighter-rouge">gpg -K</code> and seeing the key printed. Ensure the first line begins <code class="language-plaintext highlighter-rouge">sec#</code> (the # tells us the master key is not on the system), and if it doesn’t ensure you are importing the subkeys and not the master key.</p>
<h2 id="testing-your-keys">Testing your keys</h2>
<p>You can test your key works for encryption and signing by trying to encrypt and decrypt something for yourself:
<code class="language-plaintext highlighter-rouge">echo "Hello world!!" | gpg -e -a -r <email> | gpg -d</code> where <email is the email address you used to create your encryption keys. If your output looks something like this your encryption subkey is working!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~$ echo "Hello world" | gpg -a -e -r benjaminboxler@gmail.com | gpg -d
gpg: automatically retrieved 'ben@example.com' via Local
gpg: encrypted with 2048-bit RSA key, ID B470770583B4730F, created 2019-01-25
"Ben Boxler <benjaminboxler@gmail.com>"
Hello world
</code></pre></div></div>
<p>To test the signing subkey, we can do something very similar:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~$ echo "Hello world" | gpg -a --clearsign | gpg --verify
gpg: using "C2C3E54703912E3642F0ADED43BF0FED54D604EB" as default secret key for signing
gpg: Signature made Thu 31 Jan 00:31:29 2019 GMT
gpg: using RSA key 3A1A690F27296E2CA4F81F4B79FB2769D32052E7
gpg: Good signature from "Ben Boxler <benjaminboxler@gmail.com>" [ultimate]
gpg: aka "Benjamin Boxler <benjamin@boxler.co.uk>" [ultimate]
gpg: aka "Ben Boxler (protonmail.com) <ben@boxler.me>" [ultimate]
</code></pre></div></div>
<p>If both of those worked, you can now use your keys as you wish!</p>
<h2 id="adding-other-peoples-keys">Adding Other People’s Keys</h2>
<p>The best way to add someone elses key is to get it directly from them, sneakernet or other means. Following that is to get their private key from a trusted source - for example I host my private key on <a href="/54D604EB.txt">this website</a> which is verifyable as mine by a <a href="https://github.com/benjaminboxler/benjaminboxler.github.io/commit/b1bf959d35c86ce015b822a76b1d439b5ebc49c9">signed git commit here</a>. Failing that, you can use their full fingerprint (preferred), short fingerprint (<a href="https://gwolf.org/node/4070">can be exploited</a>), or email address (you probably just shouldn’t) along with gpg’s <code class="language-plaintext highlighter-rouge">--search-keys</code> option.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gpg --search-keys 54d604eb
gpg: data source: http://pgpkeys.uk:11371
(1) Ben Boxler <benjaminboxler@gmail.com>
Benjamin Boxler <benjamin@boxler.co.uk>
Ben Boxler (protonmail.com) <ben@boxler.me>
2048 bit RSA key 43BF0FED54D604EB, created: 2017-10-30, expires: 2019-10-30
Keys 1-1 of 1 for "54d604eb". Enter number(s), N)ext, or Q)uit >
</code></pre></div></div>
<p>You now have my keys, and can verify my signature or encrypt things for me! Give it a go and send me an encrypted email at <a href="mailto:benjaminboxler@gmail.com">benjaminboxler@gmail.com</a> and let me know how it goes!</p>
<p>In a part two of this, I’m going to go through using Yubikey and SSH with your new GPG credentials.</p>Getting Started with PGP and GPG PGP is the best way of getting started with digital security, as it is the most well used and espablished form of encryption. It is often used by journalists and whistleblowers to communicate securely. It is based around a network of trust where Bob creates a key, and becasue Alice knows him in person, and so does Robby, and because Sarah trusts Alice, she trusts that the key signed by her does really belong to Bob.Using Git Mergetools with Unity2018-08-01T00:00:00+00:002018-08-01T00:00:00+00:00http://boxler.me/github/git/unity/2018/08/01/mergetool<h1 id="setting-up-git-mergetools-for-unity">Setting up git mergetools for Unity</h1>
<p>If you have Git set up with Unity, you will more often than not find merge conflicts ruining your Unity scenes and prefabs, removing changes and sometimes losing data. Luckily Unity includes a utility to automatically merge these conflicts, and despite it being seldom mentioned in the documentation, works very well.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Unity (The tool has been packaged since about 4.5, but try and use a newer version of Unity if at all possible)</li>
<li>Git CLI - available <a href="https://git-scm.com">here</a>. I’m not going through how to install it here.</li>
<li>A fallback mergetool. I recommend <a href="https://www.perforce.com/products/helix-core-apps/merge-diff-tool-p4merge">Perforce Merge</a>. Just accept all defaults during installation.</li>
</ul>
<h2 id="setup">Setup</h2>
<p>In unity inside the editor settings (Edit > Project Settings > Editor), under <strong>Version Control</strong> you must set Mode to <strong>Visible Meta Files</strong> and under <strong>Asset Serialization</strong> you must set Mode to <strong>Force Text</strong>. This will ensure all binary files are serialised as Ascii files.</p>
<p>In Git Bash, type</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>notepad ~/.gitconfig
</code></pre></div></div>
<p>And append the following lines:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[merge]
tool = unityyamlmerge
[mergetool "unityyamlmerge"]
trustExitCode = false
keepBackup = false
cmd = 'C:\\Program Files\\Unity\\Editor\\Data\\Tools\\UnityYAMLMerge.exe' merge -p "$BASE" "$REMOTE" "$LOCAL" "$MERGED"
</code></pre></div></div>
<p>What this does she isn’t overly important, but know that the path in <strong>CMD</strong> must match your Unity path (It’ll be different on MacOS), but ensure the double slashes remain. The path is probably correct on most default setups.</p>
<p>This should be all the setup required provided p4merge was installed with default location.</p>
<h2 id="using-git-mergetool">Using Git Mergetool</h2>
<p>Next time you run git merge and are faced with a conflict, all you need to do is run</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git mergetool
</code></pre></div></div>
<p>And it will automatically resolve most conflicts. On the off chance that it can’t automatically merge it will open p4merge and highlight the conflicts. Inside p4merge all you need to go is select the version you want (left, right or hold shift and select both) for each conflict inside the file, hit save and close the window and it will resolve the conflicts. Back inside the terminal you just need to run</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git merge --continue
</code></pre></div></div>
<p>And the merge will finish successfully.</p>
<p>Good luck and happy merging.</p>Setting up git mergetools for Unity If you have Git set up with Unity, you will more often than not find merge conflicts ruining your Unity scenes and prefabs, removing changes and sometimes losing data. Luckily Unity includes a utility to automatically merge these conflicts, and despite it being seldom mentioned in the documentation, works very well.Using Spatial Understanding to find custom room elements2018-07-10T00:00:00+00:002018-07-10T00:00:00+00:00http://boxler.me/hololens/coding/2018/07/10/SpatialUnderstandingShapes<h1 id="using-spatial-understanding-to-find-custom-room-elements">Using Spatial Understanding to find custom room elements</h1>
<p>Having just spent <del>two</del> nearly three days trying to get the Hololens to see a table, I decided to write this due to the lack of documentation surrounding these features available on the web.</p>
<p>This guide assumes you already have the <a href="https://github.com/Microsoft/MixedRealityToolkit-Unity/releases/tag/2017.4.0.0">Hololens Toolkit</a> imported in your project, and know your way around Unity at least a little. I’m using Unity 2017.4 but a later version will also work (Apart from maybe 2018.2b for now).</p>
<h2 id="setting-up-your-scene">Setting up your scene</h2>
<p>Two prefabs are required in your scene before you can start any of this, and they are <code class="language-plaintext highlighter-rouge">SpatialMapping</code> and <code class="language-plaintext highlighter-rouge">SpatialUnderstanding</code>, both of which can be found inside the HoloToolkit (Use the search box to find them, it’s easier). Drag these into your scene, and then on the <code class="language-plaintext highlighter-rouge">SpatialMapping</code> object you will need to untick <code class="language-plaintext highlighter-rouge">Draw Visual Meshes</code> inside the <code class="language-plaintext highlighter-rouge">Spatial Mapping Manger</code> component. Optionally you can also change the <code class="language-plaintext highlighter-rouge">Mesh material</code> on the <code class="language-plaintext highlighter-rouge">SpatialUnderstanding</code> script to be <code class="language-plaintext highlighter-rouge">Wireframe.mat</code> which, while not necessary, will make it a lot easier to see what’s going on when it starts generating meshes over everything.</p>
<p>Now you should also create an Empty Object in the scene and call it <code class="language-plaintext highlighter-rouge">MappingController</code> or something equally meaningful. You will want to create a new script component on this in C# and call it ScanManager.</p>
<h2 id="mappingcontrollercs">MappingController.cs</h2>
<p>Inside your <code class="language-plaintext highlighter-rouge">MappingController.cs</code> file the first thing you want to do is to tell the compiler you will be using things from various packages, so replace the three default imports with the following:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Collections.ObjectModel</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">HoloToolkit.Unity</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">HoloToolkit.Unity.InputModule</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">UnityEngine</span><span class="p">;</span>
</code></pre></div></div>
<p>I will point out where each import is used as we reach that part in the code. After this, you want to implement <code class="language-plaintext highlighter-rouge">IInputClickHandler</code> from the <code class="language-plaintext highlighter-rouge">HoloToolkit.Unity.InputModule</code> package and then implement the function <code class="language-plaintext highlighter-rouge">OnInputClicked(InputClickedEventData eventData)</code>. Inside this function just add the lines</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Requesting scan finish"</span><span class="p">);</span>
<span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="nf">RequestFinishScan</span><span class="p">();</span>
</code></pre></div></div>
<p>This is used so when we airtap, the scan will begin to finish.</p>
<p>At the top of the class let’s just accept a prefab from the Inspector:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">SerializeField</span><span class="p">]</span>
<span class="k">private</span> <span class="n">GameObject</span> <span class="n">prefab</span><span class="p">;</span>
</code></pre></div></div>
<p>Inside your <code class="language-plaintext highlighter-rouge">Start()</code> function you want to add two lines:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">InputManager.Instance.PushFallBackHandler(gameObject)</code>
What this does is makes this gameObject (notice the capitalisation) the handler for any click events that don’t have focus on any other object . This also comes from HoloToolkit’s Input module.</li>
<li><code class="language-plaintext highlighter-rouge">SpatialUnderstanding.Instance.ScanStateChanged += ScanStateChanged</code>.
This is going to throw an error but we will fix that in just a moment.
This line adds our function <code class="language-plaintext highlighter-rouge">ScanStateChanged()</code> to be called whenever <code class="language-plaintext highlighter-rouge">SpatialUnderstanding.Instance.ScanStateChanged</code> gets called. This delegate pattern allows for functions to be called on classes that have no knowledge of each other.</li>
</ol>
<p>Now we have to create our <code class="language-plaintext highlighter-rouge">ScanStateChanged()</code> function we referenced in <code class="language-plaintext highlighter-rouge">Start()</code>. This function has to be <code class="language-plaintext highlighter-rouge">public</code> (It’s being called from outside of our class) with a return type of <code class="language-plaintext highlighter-rouge">void</code>.
Inside this function we will add the following lines:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">ScanStateChanged</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">ScanState</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">case</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">ScanStates</span><span class="p">.</span><span class="n">Scanning</span><span class="p">:</span>
<span class="nf">LogSurfaceState</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">ScanStates</span><span class="p">.</span><span class="n">Done</span><span class="p">:</span>
<span class="nf">InstantiateObjectOnTable</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">default</span><span class="p">:</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now whilst the same could be done with an <code class="language-plaintext highlighter-rouge">if/else if</code> statement, this allows for further expansion if you wanted to listen for other states such as <code class="language-plaintext highlighter-rouge">ReadyToScan</code> or <code class="language-plaintext highlighter-rouge">Finishing</code>. Again you are going to get an error around the two function calls, so let’s create them with return type <code class="language-plaintext highlighter-rouge">void</code> and leave them empty for now.
Creating your <code class="language-plaintext highlighter-rouge">LogSurfaceState()</code> function is completely optional, in fact you will almost definitely want to remove it from your application before publishing.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">void</span> <span class="nf">LogSurfaceState</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">IntPtr</span> <span class="n">statPtr</span> <span class="p">=</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">UnderstandingDLL</span><span class="p">.</span><span class="nf">GetStaticPlayspaceStatsPtr</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">SpatialUnderstandingDll</span><span class="p">.</span><span class="n">Imports</span><span class="p">.</span><span class="nf">QueryPlayspaceStats</span><span class="p">(</span><span class="n">statPtr</span><span class="p">)</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">stats</span> <span class="p">=</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">UnderstandingDLL</span><span class="p">.</span><span class="nf">GetStaticPlayspaceStats</span><span class="p">();</span>
<span class="n">Debug</span><span class="p">.</span><span class="n">Log</span> <span class="p">=</span> <span class="n">String</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"TotalSurfaceArea: {0:0.##}\n"</span> <span class="p">+</span>
<span class="s">"WallSurfaceArea: {1:0.##}\n"</span> <span class="p">+</span>
<span class="s">"HorizSurfaceArea: {2:0.##}"</span><span class="p">,</span>
<span class="n">stats</span><span class="p">.</span><span class="n">TotalSurfaceArea</span><span class="p">,</span>
<span class="n">stats</span><span class="p">.</span><span class="n">WallSurfaceArea</span><span class="p">,</span>
<span class="n">stats</span><span class="p">.</span><span class="n">HorizSurfaceArea</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Whilst this all looks a little messy, all it is doing is gathering all of the data about how much data has been collected about the space and how much of it is horizontal vs vertical space. In my apps I have this as a little tagalong <code class="language-plaintext highlighter-rouge">3DTextMesh</code> that I can check whilst wearing the Hololens.</p>
<h2 id="defining-custom-shapes">Defining custom shapes</h2>
<p>Let’s create a private function called <code class="language-plaintext highlighter-rouge">AddShapeDefinition</code> and give it the following signature.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">void</span> <span class="nf">AddShapeDefinition</span><span class="p">(</span><span class="kt">string</span> <span class="n">shape</span> <span class="n">name</span><span class="p">,</span>
<span class="n">List</span><span class="p"><</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeComponent</span><span class="p">></span> <span class="n">shapeComponents</span><span class="p">,</span>
<span class="n">List</span><span class="p"><</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeConstraints</span><span class="p">></span> <span class="n">shapeConstraints</span><span class="p">)</span>
</code></pre></div></div>
<p>The shapeName is going top be the name the shape gets instantiated using later on.</p>
<p>Let’s populate the method with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">IntPtr</span> <span class="n">shapeComponentsPtr</span> <span class="p">=</span> <span class="p">(</span><span class="n">shapeComponents</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">?</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span> <span class="p">:</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">UnderstandingDLL</span><span class="p">.</span><span class="nf">PinObject</span><span class="p">(</span><span class="n">shapeComponents</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">());</span>
<span class="n">IntPtr</span> <span class="n">shapeConstraintsPtr</span> <span class="p">=</span> <span class="p">(</span><span class="n">shapeConstraints</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">?</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span> <span class="p">:</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">UnderstandingDLL</span><span class="p">.</span><span class="nf">PinObject</span><span class="p">(</span><span class="n">shapeConstraints</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">());</span>
<span class="k">if</span> <span class="p">(</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="nf">AddShape</span><span class="p">(</span><span class="n">shapeName</span><span class="p">,</span>
<span class="p">(</span><span class="n">shapeComponents</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span> <span class="n">shapeComponents</span><span class="p">.</span><span class="n">Count</span><span class="p">,</span>
<span class="n">shapeComponentsPtr</span><span class="p">,</span>
<span class="p">(</span><span class="n">shapeConstraints</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span><span class="n">shapeConstraints</span><span class="p">.</span><span class="n">Count</span><span class="p">,</span>
<span class="n">shapeConstraintsPtr</span><span class="p">)</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Couldn't create shape. Uh oh!"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As this is just a helper method, this code might look complicated, but it doesn’t actually do much. The first two lines check whether the parameters passed in were null and returns a pointer for them if they exist.
The line inside the <code class="language-plaintext highlighter-rouge">if</code> query uses SpatialUnderstanding’s constructor and passes in our parameters. The function returns an <em>int</em> that will be zero if it failed to create the shape.</p>
<p>Let’s create a table. <code class="language-plaintext highlighter-rouge">┬──┬ ¯\_(ツ)</code>
Create a new function <code class="language-plaintext highlighter-rouge">private void CreateTableShape()</code> and we will populate it below.
As we saw above there are two types which will define what a ‘table’ is to the Hololens:
<code class="language-plaintext highlighter-rouge">SpatialUnderstandingDllShapes.ShapeComponent</code> and <code class="language-plaintext highlighter-rouge">SpatialUnderstandingDllShapes.ShapeConstraint</code>.</p>
<p>A component defines a plane of a shape and a shape can have several planes - for example a set of steps may have two or more components, one for each shape. Each component is made by defining a set of rules. Let’s have a look at the two simple rules used to define our table:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">shapeComponents</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeComponent</span><span class="p">>()</span>
<span class="p">{</span>
<span class="k">new</span> <span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="nf">ShapeComponent</span><span class="p">(</span>
<span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeComponentConstraint</span><span class="p">>()</span>
<span class="p">{</span>
<span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeComponentConstraint</span><span class="p">.</span><span class="nf">Create_SurfaceHeight_Between</span><span class="p">(</span><span class="m">0.6f</span><span class="p">,</span> <span class="m">1.5f</span><span class="p">),</span>
<span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeComponentConstraint</span><span class="p">.</span><span class="nf">Create_IsRectangle</span><span class="p">(),</span>
<span class="p">}),</span>
<span class="p">};</span>
</code></pre></div></div>
<p>As you can see here there are only two rules in play - one tells the Hololens to look for something between 0.6 and 1.5m off the ground, the second tells it to look for that something to be rectangular. The <code class="language-plaintext highlighter-rouge">Create_IsRectangle()</code> call does also accept a single parameter, a value between 0-1 that defines how similarly to a rectangle the shape is, it defaults to 0.5 and going too far over that will give you difficulty detecting the shape outside of perfect situations.
It would probably also be wise to add size constraints such as <code class="language-plaintext highlighter-rouge">Create_SurfaceAreaMin(float)</code> or <code class="language-plaintext highlighter-rouge">Create_RectangleLengthMin(float),</code> but we’re going to ignore those here.</p>
<p>In SpatialUnderstanding, a constraint is a relationship between one or more components. In our example it only applies to the one component we have. Other constraints will relate the size or positions of two components, for example <code class="language-plaintext highlighter-rouge">Create_RectanglesParallel(...)</code> which takes the index of two components from the components <code class="language-plaintext highlighter-rouge">List<></code> and ensures it only matches to two parallel shapes. This would be useful if someone was trying to identify a picnic bench, for example, but perhaps not a staircase that can turn corners.
Add the below lines to our <code class="language-plaintext highlighter-rouge">CreateTableShape</code> function below the components.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">shapeConstraints</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeConstraint</span><span class="p">>()</span>
<span class="p">{</span>
<span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeConstraint</span><span class="p">.</span><span class="nf">Create_NoOtherSurface</span><span class="p">(),</span>
<span class="p">};</span>
</code></pre></div></div>
<p>As we can see, our table only needs one constraint, <code class="language-plaintext highlighter-rouge">NoOtherSurface()</code>, rule which tells the Hololens that there can’t be any other objects on top of the table. How forgiving that is isn’t clear.</p>
<p>Fortunately these few lines are all you need to define a very simple shape such as a table, and we can therefore pass these two lists into the helper function we created earlier:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">AddShapeDefinition</span><span class="p">(</span><span class="err">“</span><span class="n">Table</span><span class="err">”</span><span class="p">,</span> <span class="n">shapeComponents</span><span class="p">,</span> <span class="n">shapeConstraints</span><span class="p">);</span>
</code></pre></div></div>
<p>We can also add the following line to this function, but we will need to be aware that this then requires the function to be called only after the SpatialUnderstanding has finished running.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="nf">ActivateShapeAnalysis</span><span class="p">()</span>
</code></pre></div></div>
<p>This function can only be called after shapes have been defined and your <code class="language-plaintext highlighter-rouge">SpatialUnderstanding.Instance.ScanState</code> is equal to <code class="language-plaintext highlighter-rouge">Finished</code>.</p>
<p>So in our <code class="language-plaintext highlighter-rouge">ScanStateChanged()</code> function, let’s add the line <code class="language-plaintext highlighter-rouge">CreateTableShape()</code> just before our call to <code class="language-plaintext highlighter-rouge">InstantiateObjectOnTable</code>.</p>
<h2 id="locating-these-shapes">Locating these shapes</h2>
<p>Now we can finally create our private function <code class="language-plaintext highlighter-rouge">InstantiateObjectOnTable()</code> and get rid of that error. I’m going to go ahead a put the entire function here, then go through it line by line:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">void</span> <span class="nf">InstantiateObjectOnSurface</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">MaxResultCount</span> <span class="p">=</span> <span class="m">512</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">shapeResults</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeResult</span><span class="p">[</span><span class="n">MaxResultCount</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">resultsShapePtr</span> <span class="p">=</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">UnderstandingDLL</span><span class="p">.</span><span class="nf">PinObject</span><span class="p">(</span><span class="n">shapeResults</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">locationCount</span> <span class="p">=</span> <span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="nf">QueryShape_FindPositionsOnShape</span><span class="p">(</span><span class="s">"Table"</span><span class="p">,</span> <span class="m">0.1f</span><span class="p">,</span> <span class="n">shapeResults</span><span class="p">.</span><span class="n">Length</span><span class="p">,</span> <span class="n">resultsShapePtr</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">locationCount</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">shapeResult</span> <span class="p">=</span> <span class="n">shapeResults</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
<span class="nf">Instantiate</span><span class="p">(</span><span class="n">prefab</span><span class="p">,</span> <span class="n">shapeResult</span><span class="p">.</span><span class="n">position</span><span class="p">,</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">LookRotation</span><span class="p">(</span><span class="n">shapeResult</span><span class="p">.</span><span class="n">position</span><span class="p">.</span><span class="n">normalized</span><span class="p">,</span> <span class="n">Vector3</span><span class="p">.</span><span class="n">up</span><span class="p">));</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Placed Hologram"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Not enough space for the hologram"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The first line simply is used to define an upper limit for the number of shapes we can store. Is 512 overkill? Of course, but modern computers and even the hololens have more than enough memory for for this.</p>
<p>The next line is to create an empty array of ShapeResult type, of the size we defined above. This line is then passed into the next line into <code class="language-plaintext highlighter-rouge">PinObject</code> which is used to reserve the memory to then be accessed in the future without risk of being written to by another program.</p>
<p>The next line is where the magic happens. <code class="language-plaintext highlighter-rouge">Query_FindPositionsOnShape()</code> accepts the name of our created shape, the minimum space on the shape, and then the length of our shape array, and a pointer to our array it can write to.
This function returns the number of shapes it finds and this number will never exceed the <code class="language-plaintext highlighter-rouge">shapeResult.length</code> we passed in.
Below this we check if this function returned anything. If so we grab that first shape (there are other ways of doing this, be creative) and Instantiate our prefab in that place.</p>
<p>This should then instantiate a prefab on your table if you try it out! Caveats: obviously you need a hololens to do this. That goes without saying. Also your prefab needs it’s origin at the bottom of it, unless you either A) transform it after instantiation, or B) don’t mind it being inside the table. 🤷♂️</p>
<p>Is this the most effective, extensible and reusable way to do things? No, of course not. Ideally you would have some kind of manager doing all of the shape stuff and make calls to that. But does this work? Yeah, and it works more than well enough for a small project or prototype.</p>
<p>The full code can be found below.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Collections.ObjectModel</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">HoloToolkit.Unity</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">HoloToolkit.Unity.InputModule</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">UnityEngine</span><span class="p">;</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ScanManager</span> <span class="p">:</span> <span class="n">MonoBehaviour</span><span class="p">,</span> <span class="n">IInputClickHandler</span> <span class="p">{</span>
<span class="p">[</span><span class="n">SerializeField</span><span class="p">]</span>
<span class="k">private</span> <span class="n">GameObject</span> <span class="n">prefab</span><span class="p">;</span>
<span class="k">void</span> <span class="nf">start</span> <span class="p">()</span> <span class="p">{</span>
<span class="n">InputManager</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="nf">PushFallbackInputHandler</span><span class="p">(</span><span class="n">gameObject</span><span class="p">);</span>
<span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">ScanStateChanged</span> <span class="p">+=</span> <span class="n">ScanStateChanged</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">void</span> <span class="nf">OnDestroy</span> <span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">ScanStateChanged</span> <span class="p">-=</span> <span class="n">ScanStateChanged</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnInputClicked</span> <span class="p">(</span><span class="n">InputClickedEventData</span> <span class="n">eventData</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Requesting scan finish"</span><span class="p">);</span>
<span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="nf">RequestFinishScan</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">LogSurfaceState</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">IntPtr</span> <span class="n">statPtr</span> <span class="p">=</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">UnderstandingDLL</span><span class="p">.</span><span class="nf">GetStaticPlayspaceStatsPtr</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">SpatialUnderstandingDll</span><span class="p">.</span><span class="n">Imports</span><span class="p">.</span><span class="nf">QueryPlayspaceStats</span><span class="p">(</span><span class="n">statPtr</span><span class="p">)</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">stats</span> <span class="p">=</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">UnderstandingDLL</span><span class="p">.</span><span class="nf">GetStaticPlayspaceStats</span><span class="p">();</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="n">String</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"TotalSurfaceArea: {0:0.##}\n"</span> <span class="p">+</span>
<span class="s">"WallSurfaceArea: {1:0.##}\n"</span> <span class="p">+</span>
<span class="s">"HorizSurfaceArea: {2:0.##}"</span><span class="p">,</span>
<span class="n">stats</span><span class="p">.</span><span class="n">TotalSurfaceArea</span><span class="p">,</span>
<span class="n">stats</span><span class="p">.</span><span class="n">WallSurfaceArea</span><span class="p">,</span>
<span class="n">stats</span><span class="p">.</span><span class="n">HorizSurfaceArea</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">ScanStateChanged</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">ScanState</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">case</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">ScanStates</span><span class="p">.</span><span class="n">Scanning</span><span class="p">:</span>
<span class="nf">LogSurfaceState</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">ScanStates</span><span class="p">.</span><span class="n">Done</span><span class="p">:</span>
<span class="nf">CreateTableShape</span><span class="p">();</span>
<span class="nf">InstantiateObjectOnTable</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">default</span><span class="p">:</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">void</span> <span class="nf">AddShapeDefinition</span><span class="p">(</span><span class="kt">string</span> <span class="n">shape</span> <span class="n">name</span><span class="p">,</span>
<span class="n">List</span><span class="p"><</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeComponent</span><span class="p">></span> <span class="n">shapeComponents</span><span class="p">,</span>
<span class="n">List</span><span class="p"><</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeConstraint</span><span class="p">></span> <span class="n">shapeConstraints</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">IntPtr</span> <span class="n">shapeComponentsPtr</span> <span class="p">=</span> <span class="p">(</span><span class="n">shapeComponents</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">?</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span> <span class="p">:</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">UnderstandingDLL</span><span class="p">.</span><span class="nf">PinObject</span><span class="p">(</span><span class="n">shapeComponents</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">());</span>
<span class="n">IntPtr</span> <span class="n">shapeConstraintsPtr</span> <span class="p">=</span> <span class="p">(</span><span class="n">shapeConstraints</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">?</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span> <span class="p">:</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">UnderstandingDLL</span><span class="p">.</span><span class="nf">PinObject</span><span class="p">(</span><span class="n">shapeConstraints</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">());</span>
<span class="k">if</span> <span class="p">(</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="nf">AddShape</span><span class="p">(</span><span class="n">shapeName</span><span class="p">,</span>
<span class="p">(</span><span class="n">shapeComponents</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span> <span class="n">shapeComponents</span><span class="p">.</span><span class="n">Count</span><span class="p">,</span>
<span class="n">shapeComponentsPtr</span><span class="p">,</span>
<span class="p">(</span><span class="n">shapeConstraints</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span><span class="n">shapeConstraints</span><span class="p">.</span><span class="n">Count</span><span class="p">,</span>
<span class="n">shapeConstraintsPtr</span><span class="p">)</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Couldn't create shape. Uh oh!"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">CreateTableShape</span> <span class="p">()</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">shapeComponents</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeComponent</span><span class="p">>()</span>
<span class="p">{</span>
<span class="k">new</span> <span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="nf">ShapeComponent</span><span class="p">(</span>
<span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeComponentConstraint</span><span class="p">>()</span>
<span class="p">{</span>
<span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeComponentConstraint</span><span class="p">.</span><span class="nf">Create_SurfaceHeight_Between</span><span class="p">(</span><span class="m">0.6f</span><span class="p">,</span> <span class="m">1.5f</span><span class="p">),</span>
<span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeComponentConstraint</span><span class="p">.</span><span class="nf">Create_IsRectangle</span><span class="p">(),</span>
<span class="p">}),</span>
<span class="p">};</span>
<span class="kt">var</span> <span class="n">shapeConstraints</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeConstraint</span><span class="p">>()</span>
<span class="p">{</span>
<span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeConstraint</span><span class="p">.</span><span class="nf">Create_NoOtherSurface</span><span class="p">(),</span>
<span class="p">};</span>
<span class="nf">AddShapeDefinition</span><span class="p">(</span><span class="s">"Table"</span><span class="p">,</span> <span class="n">shapeComponents</span><span class="p">,</span> <span class="n">shapeConstraints</span><span class="p">);</span>
<span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="nf">ActivateShapeAnalysis</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">InstantiateObjectOnTable</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">MaxResultCount</span> <span class="p">=</span> <span class="m">512</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">shapeResults</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="n">ShapeResult</span><span class="p">[</span><span class="n">MaxResultCount</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">resultsShapePtr</span> <span class="p">=</span> <span class="n">SpatialUnderstanding</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">UnderstandingDLL</span><span class="p">.</span><span class="nf">PinObject</span><span class="p">(</span><span class="n">shapeResults</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">locationCount</span> <span class="p">=</span> <span class="n">SpatialUnderstandingDllShapes</span><span class="p">.</span><span class="nf">QueryShape_FindPositionsOnShape</span><span class="p">(</span><span class="s">"Table"</span><span class="p">,</span> <span class="m">0.1f</span><span class="p">,</span> <span class="n">shapeResults</span><span class="p">.</span><span class="n">Length</span><span class="p">,</span> <span class="n">resultsShapePtr</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">locationCount</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">shapeResult</span> <span class="p">=</span> <span class="n">shapeResults</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
<span class="nf">Instantiate</span><span class="p">(</span><span class="n">prefab</span><span class="p">,</span> <span class="n">shapeResult</span><span class="p">.</span><span class="n">position</span><span class="p">,</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">LookRotation</span><span class="p">(</span><span class="n">shapeResult</span><span class="p">.</span><span class="n">position</span><span class="p">.</span><span class="n">normalized</span><span class="p">,</span> <span class="n">Vector3</span><span class="p">.</span><span class="n">up</span><span class="p">));</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Placed Hologram"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Not enough space for the hologram"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>Using Spatial Understanding to find custom room elements Having just spent two nearly three days trying to get the Hololens to see a table, I decided to write this due to the lack of documentation surrounding these features available on the web.