{"id":37738,"date":"2018-08-20T16:04:07","date_gmt":"2018-08-20T20:04:07","guid":{"rendered":"https:\/\/blog.xamarin.com\/?p=37738"},"modified":"2019-03-25T14:16:29","modified_gmt":"2019-03-25T22:16:29","slug":"exploring-new-ios-12-arkit-capabilities-with-xamarin","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/xamarin\/exploring-new-ios-12-arkit-capabilities-with-xamarin\/","title":{"rendered":"Exploring New iOS 12 ARKit Capabilities With Xamarin"},"content":{"rendered":"<p>\t\t\t\tIt&#8217;s summertime, which for Xamarin developers means new iOS betas to explore and learn. ARKit, which debuted last year in iOS 11, has matured over the past year and in iOS 12 is much more flexible than it was previously.<\/p>\n<h2>New iOS 12 ARKit<\/h2>\n<p>If you haven&#8217;t looked at ARKit in a year, the truly big change came from a point-release in the spring, when Apple added the capability to detect vertical and horizontal planes. Detected planes are the major junction between computer-generated geometry and the real world. Most often, you&#8217;ll position your computer imagery relative to such planes.<\/p>\n<p><img decoding=\"async\" class=\"aligncenter wp-image-37744\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/44\/2019\/03\/lobrien_point_cloud.jpeg\" alt=\"Creating an ARReferenceObject\" width=\"324\" height=\"700\" \/><\/p>\n<h3>Working With Point Clouds<\/h3>\n<p>These planes are built upon a lower-level feature; a &#8220;point cloud&#8221; generated by the system using &#8220;<a href=\"https:\/\/en.wikipedia.org\/wiki\/Visual_odometry#Visual_Inertial_Odometry\">visual inertial odometry<\/a>.&#8221;<\/p>\n<p><em><strong>Fun fact:<\/strong> the &#8216;inertial&#8217; part of that includes exploiting the slight shifts inevitable from holding the device in your hand, so ARKit doesn&#8217;t work as well if you put the device on a tripod.<\/em><\/p>\n<p>This point cloud is available to developers in the <a href=\"https:\/\/developer.xamarin.com\/api\/property\/ARKit.ARFrame.RawFeaturePoints\/\"><code>ARFrame.RawFeaturePoints<\/code><\/a> property. The\u00a0<a href=\"https:\/\/github.com\/xamarin\/ios-samples\/tree\/master\/ios12\/ScanningAndDetecting3DObjects\">Scanning and Detecting 3D Objects<\/a>\u00a0sample shows how the point cloud can be used to create and save an <a href=\"https:\/\/developer.xamarin.com\/api\/type\/ARKit.ARReferenceObject\/\"><code>ARReferenceObject<\/code><\/a> for use in other ARKit apps. For instance, you could create an <code>ARReferenceObject<\/code> of a sculpture for use in a museum app or an engine part for use in a DIY car-repair app.<\/p>\n<h3>Three-Part Performance Strategy<\/h3>\n<p>Visualizing the point cloud efficiently can be a tricky problem. Iterating over the points, then creating and placing a new SceneKit node for each point would kill the frame rate. Alternatively, if done asynchronously there would be a lag. The sample maintains performance with a three-part strategy:<\/p>\n<ul>\n<li>Using <code>unsafe<\/code> code to pin the data in place and interpret the data as a raw buffer of bytes.<\/li>\n<li>Converting that raw buffer into an <a href=\"https:\/\/developer.xamarin.com\/api\/type\/SceneKit.SCNGeometrySource\/\"><code>SCNGeometrySource<\/code><\/a> and creating a &#8220;template&#8221; <a href=\"https:\/\/developer.xamarin.com\/api\/type\/SceneKit.SCNGeometryElement\/\"><code>SCNGeometryElement<\/code><\/a> object.<\/li>\n<li>Rapidly &#8220;stitching together&#8221; the raw data and the template using <a href=\"https:\/\/developer.xamarin.com\/api\/member\/SceneKit.SCNGeometry.Create\/p\/SceneKit.SCNGeometrySource[]\/SceneKit.SCNGeometryElement[]\/\"><code>SCNGeometry.Create(SCNGeometrySource[], SCNGeometryElement[])<\/code><\/a><\/li>\n<\/ul>\n<pre class=\"EnlighterJSRAW\">internal static SCNGeometry CreateVisualization(NVector3[] points, UIColor color, float size)\r\n{\r\n  if (points.Length == 0)\r\n  {\r\n    return null;\r\n  }\r\n\r\n  unsafe\r\n  {\r\n    var stride = sizeof(float) * 3;\r\n\r\n    \/\/ Pin the data down so that it doesn't move\r\n    fixed (NVector3* pPoints = &amp;points[0])\r\n    {\r\n      \/\/ Important: Don't unpin until after `SCNGeometry.Create`, because geometry creation is lazy\r\n  \r\n      \/\/ Grab a pointer to the data and treat it as a byte buffer of the appropriate length\r\n      var intPtr = new IntPtr(pPoints);\r\n      var pointData = NSData.FromBytes(intPtr, (System.nuint) (stride * points.Length));\r\n\r\n      \/\/ Create a geometry source (factory) configured properly for the data (3 vertices)\r\n      var source = SCNGeometrySource.FromData(\r\n        pointData,\r\n        SCNGeometrySourceSemantics.Vertex,\r\n        points.Length,\r\n        true,\r\n        3,\r\n        sizeof(float),\r\n        0,\r\n        stride\r\n      );\r\n\r\n      \/\/ Create geometry element\r\n      \/\/ The null and bytesPerElement = 0 look odd, but this is just a template object\r\n      var template = SCNGeometryElement.FromData(null, SCNGeometryPrimitiveType.Point, points.Length, 0);\r\n      template.PointSize = 0.001F;\r\n      template.MinimumPointScreenSpaceRadius = size;\r\n      template.MaximumPointScreenSpaceRadius = size;\r\n\r\n      \/\/ Stitch the data (source) together with the template to create the new object\r\n      var pointsGeometry = SCNGeometry.Create(new[] { source }, new[] { template });\r\n      pointsGeometry.Materials = new[] { Utilities.Material(color) };\r\n      return pointsGeometry;\r\n    }\r\n  }\r\n}<\/pre>\n<h2>Advanced iOS Gestures<\/h2>\n<p>Another tricky challenge relates to using gestures to manipulate AR objects. The app allows the user to drag, rotate, scale, and extrude sides of the bounding box that surrounds the object being scanned. First, the gestures are threshold-aware and only activate after the user has moved a certain amount of pixels &#8212; the code in the <strong>Gesture_Recognizers<\/strong> subdirectory is highly reusable and makes a nice addition to your catalog.<\/p>\n<h3>Dragging<\/h3>\n<p>Even more interesting is the challenge of dragging the bounding box along a real-world plane. Such as the surface on which the scanned object sits. In order to constrain the drag to a specific axis (say the X-axis), the easiest way is to construct a new plane that is:<\/p>\n<ol>\n<li>Aligned to the drag axis.<\/li>\n<li>Orthogonal to the vector from the camera to the object.<\/li>\n<\/ol>\n<p>This situation for dragging along the X-axis is shown in the following illustration. To drag along the red X-axis, while viewing the bounding box from the vector shown by the yellow arrow, we need to construct the yellow plane:<\/p>\n<p><img decoding=\"async\" class=\"aligncenter wp-image-37743\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/44\/2019\/03\/lobrien_drag_plane.png\" alt=\"Illustration of drag plane geometry\" width=\"500\" height=\"408\" \/><\/p>\n<ol>\n<li style=\"list-style-type: none\">\n<ol>\n<li>The dark yellow arrow is <code>camRayToOrigin = Origin - cameraPos<\/code><\/li>\n<li>The X-axis is <code>xVector = new SCNVector3(1, 0, 0)<\/code><\/li>\n<li>The Z-axis for the yellow plane is <code>SCNVector3.Cross(xVector, camRayToOrigin)<\/code><\/li>\n<li>The Y-axis for the yellow plane is <code>SCNVector3.Cross(xVector, zVector)<\/code><\/li>\n<li>Normalize the axes.<\/li>\n<li>Construct the transform as an <a href=\"https:\/\/developer.xamarin.com\/api\/type\/OpenTK.NMatrix4\/\">NMatrix4<\/a>:<\/li>\n<\/ol>\n<p><img decoding=\"async\" class=\"aligncenter wp-image-37742\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/44\/2019\/03\/lobrien_column_major.png\" alt=\"A column-major 4x4 transformation matrix\" width=\"500\" height=\"133\" \/>\nThis transform can be used to map the position of the user&#8217;s moving finger to slide the bounding box along the chosen drag axis (in this case, the X-axis)<\/p>\n<h3>Contructing the Proper Transform<\/h3>\n<p>The constructor for <a href=\"https:\/\/developer.xamarin.com\/api\/type\/OpenTK.NMatrix4\/\"><code>NMatrix4<\/code><\/a> is <code>NMatrix4(Vector4 row0, Vector4 row1, Vector4 row2, Vector4 row3)<\/code>, which takes as its argument the values of the <em>rows<\/em>, not the columns. So the code to construct the proper transform is:<\/p>\n<pre class=\"EnlighterJSRAW\">var transformBasedOnColumnVectors = Utilities.NMatrix4Create(new[] {\r\n  Utilities.SCNVector4Create(xVector, 0),\r\n  Utilities.SCNVector4Create(yVector, 0),\r\n  Utilities.SCNVector4Create(zVector, 0),\r\n  Utilities.SCNVector4Create(Origin, 1)\r\n  });\r\n\/\/ But we created it with row-vectors, so we need to:\r\ntransformBasedOnColumnVectors.Transpose();<\/pre>\n<p>Another option would be to extract the 4 component values of each of the 4 vectors and use the <code>NMatrix4<\/code> constructor that takes\u00a0its arguments using the\u00a0<code>M{row}{col}<\/code> notation.<\/p>\n<p>You can find the code above in the sample at <code>Ray.DragPlaneTransform(SCNVector3)<\/code>. The transform shown above is based on column-vectors and column-major matrices, that is:<\/p>\n<p><img decoding=\"async\" class=\"aligncenter wp-image-37745 size-full\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/44\/2019\/03\/lobrien_vec_mat_mult.png\" alt=\"Multiplying a matrix and a vector\" width=\"812\" height=\"168\" \/><\/p>\n<h3>Column- and Row-<\/h3>\n<p>This tends to be a common source of difficulties when dealing with Swift 3D graphics! Most of the Swift sample code uses row-major transform matrices. <a href=\"https:\/\/en.wikipedia.org\/wiki\/Row-_and_column-major_order\">Both column- and row- major interpretations are valid<\/a>. Column-major is more common in mathematical texts while row-major may be a better fit for optimizing performance. In vector-matrix multiplication, for instance, the <em>n<\/em>th element in the vector is multiplied by the <em>n<\/em>th row of the matrix. Having the row data adjacent in memory can be a performance boost with\u00a0<a href=\"https:\/\/en.wikipedia.org\/wiki\/SIMD\">SIMD<\/a> operations or from cache issues with large matrices. It has been said that &#8220;column-major is developer-friendly, while row-major is hardware-friendly.&#8221;<\/p>\n<h3>Transpose Your Matrix<\/h3>\n<p>In Swift example code, most transforms are based on SIMD such as <code>simd.float4x4<\/code>. These matrices are transposed compared to those based on column-vectors. Thus, in much Swift code, you&#8217;d find the translation component in M41, M42, and M43, rather than M14, M24, and M34. And of course, the shear and rotation components would be transposed, as well. If you have a situation where your SceneKit geometry nodes jitter, fly away, or are &#8220;stuck to the origin&#8221;, you probably need to <a href=\"https:\/\/developer.xamarin.com\/api\/member\/OpenTK.NMatrix4.Transpose()\/\"><code>Transpose()<\/code><\/a> your matrix.<\/p>\n<p>This sample demonstrates several other techniques helpful in more advanced Augmented Reality applications. Techniques such as various forms of hit-testing, serializing reference objects, and leveraging non-visible SceneKit objects for reference positioning. In addition, it demonstrates an interesting nested state-machine architecture that uses <a href=\"https:\/\/developer.xamarin.com\/api\/type\/Foundation.NSNotificationCenter\/\"><code>NSNotificationCenter<\/code><\/a> to coordinate transitions.<\/p>\n<h2>Sources and Documentation<\/h2>\n<ul>\n<li>The\u00a0<a href=\"https:\/\/github.com\/xamarin\/ios-samples\/tree\/master\/ios12\/ScanningAndDetecting3DObjects\">Xamarin Scanning and Detecting 3D Objects<\/a>\u00a0sample is a port of Apple&#8217;s <a href=\"https:\/\/developer.apple.com\/documentation\/arkit\/scanning_and_detecting_3d_objects\">Swift-based sample<\/a>.<\/li>\n<li>Additional samples are available in <a href=\"https:\/\/developer.xamarin.com\/samples\/ios\/iOS11\/\">Xamarin&#8217;s iOS Samples repository<\/a>.<\/li>\n<li>The <a href=\"https:\/\/docs.microsoft.com\/en-us\/xamarin\/ios\/platform\/introduction-to-ios11\/arkit\/\">Introduction to ARKit in Xamarin.iOS<\/a>\u00a0article is a good starting point for developers interested in Augmented Reality.<\/li>\n<li>The <a href=\"https:\/\/developer.xamarin.com\/api\/namespace\/ARKit\/\">Xamarin.iOS API documentation<\/a> provides a reference to all available types and methods.<\/li>\n<\/ul>\n<p><a href=\"https:\/\/forums.xamarin.com\/137029\/exploring-new-ios-12-arkit-capabilities-with-xamarin\/p1?new=1\">Discuss this post in the Xamarin forums.<\/a><\/p>\n<p>&nbsp;\t\t<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s summertime, which for Xamarin developers means new iOS betas to explore and learn. ARKit, which debuted last year in iOS 11, has matured over the past year and in iOS 12 is much more flexible than it was previously.<\/p>\n","protected":false},"author":556,"featured_media":40971,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[2,303],"tags":[962,963,6],"class_list":["post-37738","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-developers","category-ios","tag-ar","tag-arkit","tag-ios"],"acf":[],"blog_post_summary":"<p>It&#8217;s summertime, which for Xamarin developers means new iOS betas to explore and learn. ARKit, which debuted last year in iOS 11, has matured over the past year and in iOS 12 is much more flexible than it was previously.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts\/37738","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/users\/556"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/comments?post=37738"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts\/37738\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/media\/40971"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/media?parent=37738"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/categories?post=37738"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/tags?post=37738"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}