AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
SpatialUnderstandingCustomMesh.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 using System;
5 using System.Collections;
6 using System.Collections.Generic;
7 using UnityEngine;
9 
10 #if UNITY_WSA
11 #if UNITY_2017_2_OR_NEWER
12 using UnityEngine.XR.WSA;
13 #else
14 using UnityEngine.VR.WSA;
15 #endif
16 #endif
17 
18 namespace HoloToolkit.Unity
19 {
26  {
27  // Config
28  [Tooltip("Indicate the time in seconds between mesh imports, during the scanning phase. A value of zero will disable pulling meshes from the DLL")]
29  public float ImportMeshPeriod = 1.0f;
30 
31  [SerializeField]
32  [Tooltip("Material used to render the custom mesh generated by the DLL")]
33  private Material meshMaterial;
34 
35  public Material MeshMaterial
36  {
37  get
38  {
39  return meshMaterial;
40  }
41  set
42  {
43  meshMaterial = value;
44 
45  if (spatialUnderstanding.ScanState == SpatialUnderstanding.ScanStates.Done)
46  {
47  for (int i = 0; i < SurfaceObjects.Count; ++i)
48  {
49  SurfaceObjects[i].Renderer.sharedMaterial = meshMaterial;
50  }
51  }
52  }
53  }
54 
58  [Tooltip("Max time per frame in milliseconds to spend processing the mesh")]
59  public float MaxFrameTime = 5.0f;
60  private float MaxFrameTimeInSeconds
61  {
62  get { return (MaxFrameTime / 1000); }
63  }
64 
68  public bool CreateMeshColliders = true;
69 
70  private bool drawProcessedMesh = true;
71 
72  // Properties
76  public bool DrawProcessedMesh
77  {
78  get
79  {
80  return drawProcessedMesh;
81  }
82  set
83  {
84  drawProcessedMesh = value;
85  for (int i = 0; i < SurfaceObjects.Count; ++i)
86  {
87  SurfaceObjects[i].Renderer.enabled = drawProcessedMesh;
88  }
89  }
90  }
91 
95  public bool HasMeshSectors { get { return meshSectors != null && meshSectors.Count > 0; } }
96 
100  public bool IsImportActive { get; private set; }
101 
105  protected override Material RenderMaterial { get { return MeshMaterial; } }
106 
110  private float timeLastImportedMesh = 0;
111 
115  private SpatialUnderstanding spatialUnderstanding;
116 
121  private Dictionary<Vector3, MeshData> meshSectors = new Dictionary<Vector3, MeshData>();
122 
127  private class MeshData
128  {
132  private readonly List<Vector3> verts = new List<Vector3>();
133  private readonly List<int> tris = new List<int>();
134 
138  public readonly Mesh MeshObject = new Mesh();
139 
144  public MeshCollider SpatialCollider = null;
145 
150  public bool CreateMeshCollider = false;
151 
155  public void Reset()
156  {
157  verts.Clear();
158  tris.Clear();
159  }
160 
164  public void Commit()
165  {
166  MeshObject.Clear();
167  if (verts.Count > 2)
168  {
169  MeshObject.SetVertices(verts);
170  MeshObject.SetTriangles(tris, 0);
171  MeshObject.RecalculateNormals();
172  MeshObject.RecalculateBounds();
173  // The null assignment is required by Unity in order to pick up the new mesh
174  SpatialCollider.sharedMesh = null;
175  if (CreateMeshCollider)
176  {
177  SpatialCollider.sharedMesh = MeshObject;
178  SpatialCollider.enabled = true;
179  }
180  else
181  {
182  SpatialCollider.enabled = false;
183  }
184  }
185  }
186 
193  public void AddTriangle(Vector3 point1, Vector3 point2, Vector3 point3)
194  {
195  // Currently spatial understanding in the native layer voxellizes the space
196  // into ~2000 voxels per cubic meter. Even in a degenerate case we
197  // will use far fewer than 65000 vertices, this check should not fail
198  // unless the spatial understanding native layer is updated to have more
199  // voxels per cubic meter.
200  if (verts.Count < 65000)
201  {
202  tris.Add(verts.Count);
203  verts.Add(point1);
204 
205  tris.Add(verts.Count);
206  verts.Add(point2);
207 
208  tris.Add(verts.Count);
209  verts.Add(point3);
210  }
211  else
212  {
213  Debug.LogError("Mesh would have more vertices than Unity supports");
214  }
215  }
216  }
217 
218  private void Start()
219  {
220  spatialUnderstanding = SpatialUnderstanding.Instance;
221 #if UNITY_WSA
222  if (gameObject.GetComponent<WorldAnchor>() == null)
223  {
224  gameObject.AddComponent<WorldAnchor>();
225  }
226 #endif
227  }
228 
229  private void Update()
230  {
231  Update_MeshImport();
232  }
233 
241  private void AddTriangleToSector(Vector3 sector, Vector3 point1, Vector3 point2, Vector3 point3)
242  {
243  // Grab the mesh container we are using for this sector.
244  MeshData nextSectorData;
245  if (!meshSectors.TryGetValue(sector, out nextSectorData))
246  {
247  nextSectorData = new MeshData();
248  nextSectorData.CreateMeshCollider = CreateMeshColliders;
249 
250  int surfaceObjectIndex = SurfaceObjects.Count;
251 
252  SurfaceObject surfaceObject = CreateSurfaceObject(
253  mesh: nextSectorData.MeshObject,
254  objectName: string.Format("SurfaceUnderstanding Mesh-{0}", surfaceObjectIndex),
255  parentObject: transform,
256  meshID: surfaceObjectIndex,
257  drawVisualMeshesOverride: DrawProcessedMesh);
258 
259  nextSectorData.SpatialCollider = surfaceObject.Collider;
260 
261  AddSurfaceObject(surfaceObject);
262 
263  // Or make it if this is a new sector.
264  meshSectors.Add(sector, nextSectorData);
265  }
266 
267  // Add the vertices to the sector's mesh container.
268  nextSectorData.AddTriangle(point1, point2, point3);
269  }
270 
275  public IEnumerator Import_UnderstandingMesh()
276  {
277  var stopwatch = System.Diagnostics.Stopwatch.StartNew();
278  int startFrameCount = Time.frameCount;
279 
280  if (!spatialUnderstanding.AllowSpatialUnderstanding || IsImportActive)
281  {
282  yield break;
283  }
284 
285  IsImportActive = true;
286 
287  SpatialUnderstandingDll dll = spatialUnderstanding.UnderstandingDLL;
288 
289  Vector3[] meshVertices = null;
290  Vector3[] meshNormals = null;
291  Int32[] meshIndices = null;
292 
293  // Pull the mesh - first get the size, then allocate and pull the data
294  int vertCount;
295  int idxCount;
296 
297  if ((SpatialUnderstandingDll.Imports.GeneratePlayspace_ExtractMesh_Setup(out vertCount, out idxCount) > 0) &&
298  (vertCount > 0) &&
299  (idxCount > 0))
300  {
301  meshVertices = new Vector3[vertCount];
302  IntPtr vertPos = dll.PinObject(meshVertices);
303  meshNormals = new Vector3[vertCount];
304  IntPtr vertNorm = dll.PinObject(meshNormals);
305  meshIndices = new Int32[idxCount];
306  IntPtr indices = dll.PinObject(meshIndices);
307 
308  SpatialUnderstandingDll.Imports.GeneratePlayspace_ExtractMesh_Extract(vertCount, vertPos, vertNorm, idxCount, indices);
309  }
310 
311  // Wait a frame
312  stopwatch.Stop();
313  yield return null;
314  stopwatch.Start();
315 
316  // Create output meshes
317  if ((meshVertices != null) &&
318  (meshVertices.Length > 0) &&
319  (meshIndices != null) &&
320  (meshIndices.Length > 0))
321  {
322  // first get all our mesh data containers ready for meshes.
323  foreach (MeshData meshdata in meshSectors.Values)
324  {
325  meshdata.Reset();
326  }
327 
328  float startTime = Time.realtimeSinceStartup;
329  // first we need to split the playspace up into segments so we don't always
330  // draw everything. We can break things up in to cubic meters.
331  for (int index = 0; index < meshIndices.Length; index += 3)
332  {
333  Vector3 firstVertex = meshVertices[meshIndices[index]];
334  Vector3 secondVertex = meshVertices[meshIndices[index + 1]];
335  Vector3 thirdVertex = meshVertices[meshIndices[index + 2]];
336 
337  // The triangle may belong to multiple sectors. We will copy the whole triangle
338  // to all of the sectors it belongs to. This will fill in seams on sector edges
339  // although it could cause some amount of visible z-fighting if rendering a wireframe.
340  Vector3 firstSector = VectorToSector(firstVertex);
341 
342  AddTriangleToSector(firstSector, firstVertex, secondVertex, thirdVertex);
343 
344  // If the second sector doesn't match the first, copy the triangle to the second sector.
345  Vector3 secondSector = VectorToSector(secondVertex);
346  if (secondSector != firstSector)
347  {
348  AddTriangleToSector(secondSector, firstVertex, secondVertex, thirdVertex);
349  }
350 
351  // If the third sector matches neither the first nor second sector, copy the triangle to the
352  // third sector.
353  Vector3 thirdSector = VectorToSector(thirdVertex);
354  if (thirdSector != firstSector && thirdSector != secondSector)
355  {
356  AddTriangleToSector(thirdSector, firstVertex, secondVertex, thirdVertex);
357  }
358 
359  // Limit our run time so that we don't cause too many frame drops.
360  // Only checking every few iterations or so to prevent losing too much time to checking the clock.
361  if ((index % 30 == 0) && ((Time.realtimeSinceStartup - startTime) > MaxFrameTimeInSeconds))
362  {
363  // Debug.LogFormat("{0} of {1} processed", index, meshIndices.Length);
364  stopwatch.Stop();
365  yield return null;
366  stopwatch.Start();
367  startTime = Time.realtimeSinceStartup;
368  }
369  }
370 
371  startTime = Time.realtimeSinceStartup;
372 
373  // Now we have all of our triangles assigned to the correct mesh, we can make all of the meshes.
374  // Each sector will have its own mesh.
375  foreach (MeshData meshData in meshSectors.Values)
376  {
377  // Construct the mesh.
378  meshData.Commit();
379 
380  // Make sure we don't build too many meshes in a single frame.
381  if ((Time.realtimeSinceStartup - startTime) > MaxFrameTimeInSeconds)
382  {
383  stopwatch.Stop();
384  yield return null;
385  stopwatch.Start();
386  startTime = Time.realtimeSinceStartup;
387  }
388  }
389  }
390 
391  // Wait a frame
392  stopwatch.Stop();
393  yield return null;
394  stopwatch.Start();
395 
396  // All done - can free up marshal pinned memory
397  dll.UnpinAllObjects();
398 
399  // Done
400  IsImportActive = false;
401 
402  // Mark the timestamp
403  timeLastImportedMesh = Time.time;
404 
405  stopwatch.Stop();
406  int deltaFrameCount = (Time.frameCount - startFrameCount + 1);
407 
408  if (stopwatch.Elapsed.TotalSeconds > 0.75)
409  {
410  Debug.LogWarningFormat("Import_UnderstandingMesh took {0:N0} frames ({1:N3} ms)",
411  deltaFrameCount,
412  stopwatch.Elapsed.TotalMilliseconds
413  );
414  }
415  }
416 
423  private Vector3 VectorToSector(Vector3 vector)
424  {
425  return new Vector3(Mathf.FloorToInt(vector.x), Mathf.FloorToInt(vector.y), Mathf.FloorToInt(vector.z));
426  }
427 
432  private void Update_MeshImport()
433  {
434  // Only update every so often
435  if (IsImportActive || (ImportMeshPeriod <= 0.0f) ||
436  ((Time.time - timeLastImportedMesh) < ImportMeshPeriod) ||
437  (spatialUnderstanding.ScanState != SpatialUnderstanding.ScanStates.Scanning))
438  {
439  return;
440  }
441 
442  StartCoroutine(Import_UnderstandingMesh());
443  }
444 
445  private void OnDestroy()
446  {
447  Cleanup();
448  }
449  }
450 }
IntPtr PinObject(System.Object obj)
Pins the specified object so that the backing memory can not be relocated, adds the pinned memory han...
bool AllowSpatialUnderstanding
Switch used by the entire SpatialUnderstanding module to activate processing.
void UnpinAllObjects()
Unpins all of the memory previously pinned by calls to PinObject().
static int GeneratePlayspace_ExtractMesh_Setup([Out] out int vertexCount, [Out] out int indexCount)
Extracting the mesh is a two step process, the first generates the mesh for extraction & saves it off...
Encapsulates the primary DLL functions, including marshalling helper functions. The DLL functions are...
static T Instance
Returns the Singleton instance of the classes type. If no instance is found, then we search for an in...
Definition: Singleton.cs:26
SpatialUnderstandingDll UnderstandingDLL
Reference to the SpatialUnderstandingDLL class (wraps the understanding DLL functions).
IEnumerator Import_UnderstandingMesh()
Imports the custom mesh from the DLL. This a a coroutine which will take multiple frames to complete...
Handles the custom meshes generated by the understanding DLL. The meshes are generated during the sca...
The SpatialUnderstanding class controls the state and flow of the scanning process used in the unders...
static int GeneratePlayspace_ExtractMesh_Extract([In] int bufferVertexCount, [In] IntPtr verticesPos, [In] IntPtr verticesNormal, [In] int bufferIndexCount, [In] IntPtr indices)
Call to receive the DLL&#39;s custom generated mesh data. Use GeneratePlayspace_ExtractMesh_Setup to quer...
ScanStates ScanState
Indicates the current state of the scan process