ARKitComponent.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. using System;
  2. using System.Linq;
  3. using ARKit;
  4. using CoreMedia;
  5. using UIKit;
  6. using Urho;
  7. using Urho.Urho2D;
  8. namespace Urho.iOS
  9. {
  10. public class ARKitComponent : Component
  11. {
  12. Texture2D cameraYtexture;
  13. Texture2D cameraUVtexture;
  14. bool yuvTexturesInited;
  15. ARSessionDelegate arSessionDelegate;
  16. [Preserve]
  17. public ARKitComponent() { ReceiveSceneUpdates = true; }
  18. [Preserve]
  19. public ARKitComponent(IntPtr handle) : base(handle) { ReceiveSceneUpdates = true; }
  20. public ARConfiguration ARConfiguration { get; set; }
  21. public UIInterfaceOrientation? Orientation { get; set; }
  22. public Camera Camera { get; private set; }
  23. public ARSession ARSession { get; private set; }
  24. public string ArkitShader { get; set; } = "ARKit";
  25. public bool RunEngineFramesInARKitCallbakcs { get; set; }
  26. public override void OnAttachedToNode(Node node)
  27. {
  28. base.OnAttachedToNode(node);
  29. }
  30. public void Run(Camera camera = null)
  31. {
  32. if (Camera == null)
  33. Camera = base.Scene.GetComponent<Camera>(true);
  34. if (Camera == null)
  35. throw new InvalidOperationException("Camera was not set.");
  36. if (RunEngineFramesInARKitCallbakcs && !Application.Options.DelayedStart)
  37. throw new InvalidOperationException("ApplicationOptions.DelayedStart should be true if runEngineFramesInArkitCallbacks flag is set");
  38. if (Orientation == null)
  39. Orientation = base.Application.Options.Orientation == ApplicationOptions.OrientationType.Landscape ?
  40. UIInterfaceOrientation.LandscapeRight :
  41. UIInterfaceOrientation.Portrait;
  42. arSessionDelegate = new UrhoARSessionDelegate(this, RunEngineFramesInARKitCallbakcs);
  43. ARSession = new ARSession { Delegate = arSessionDelegate };
  44. ARConfiguration = ARConfiguration ?? new ARWorldTrackingConfiguration();
  45. ARSession.Run(ARConfiguration, ARSessionRunOptions.RemoveExistingAnchors);
  46. if (base.Application is SimpleApplication simpleApp)
  47. {
  48. simpleApp.MoveCamera = false;
  49. }
  50. if ((Orientation == UIInterfaceOrientation.LandscapeRight ||
  51. Orientation == UIInterfaceOrientation.LandscapeLeft) && ARConfiguration is ARFaceTrackingConfiguration)
  52. {
  53. throw new Exception("ARFaceTrackingConfiguration in landscape is not supported");
  54. }
  55. }
  56. protected override void OnDeleted()
  57. {
  58. base.OnDeleted();
  59. ARSession.Pause();
  60. }
  61. public event Action<ARFrame> ARFrame;
  62. public unsafe void ProcessARFrame(ARSession session, ARFrame frame)
  63. {
  64. var arcamera = frame?.Camera;
  65. var transform = arcamera.Transform;
  66. var viewportSize = new CoreGraphics.CGSize(Application.Graphics.Width, Application.Graphics.Height);
  67. float near = 0.001f;
  68. float far = 1000f;
  69. var prj = arcamera.GetProjectionMatrix(Orientation.Value, viewportSize, near, far);
  70. var dt = frame.GetDisplayTransform(Orientation.Value, viewportSize);
  71. var urhoProjection = *(Matrix4*)(void*)&prj;
  72. urhoProjection.M43 /= 2f;
  73. urhoProjection.M33 = far / (far - near);
  74. urhoProjection.M34 *= -1;
  75. //prj.M13 = 0; //center of projection
  76. //prj.M23 = 0;
  77. //urhoProjection.Row2 *= -1;
  78. urhoProjection.Transpose();
  79. Camera.SetProjection(urhoProjection);
  80. ApplyOpenTkTransform(Camera.Node, transform);
  81. if (!yuvTexturesInited)
  82. {
  83. var img = frame.CapturedImage;
  84. // texture for UV-plane;
  85. cameraUVtexture = new Texture2D();
  86. cameraUVtexture.SetNumLevels(1);
  87. cameraUVtexture.SetSize((int)img.GetWidthOfPlane(1), (int)img.GetHeightOfPlane(1), Graphics.LuminanceAlphaFormat, TextureUsage.Dynamic);
  88. cameraUVtexture.FilterMode = TextureFilterMode.Bilinear;
  89. cameraUVtexture.SetAddressMode(TextureCoordinate.U, TextureAddressMode.Clamp);
  90. cameraUVtexture.SetAddressMode(TextureCoordinate.V, TextureAddressMode.Clamp);
  91. cameraUVtexture.Name = nameof(cameraUVtexture);
  92. Application.ResourceCache.AddManualResource(cameraUVtexture);
  93. // texture for Y-plane;
  94. cameraYtexture = new Texture2D();
  95. cameraYtexture.SetNumLevels(1);
  96. cameraYtexture.FilterMode = TextureFilterMode.Bilinear;
  97. cameraYtexture.SetAddressMode(TextureCoordinate.U, TextureAddressMode.Clamp);
  98. cameraYtexture.SetAddressMode(TextureCoordinate.V, TextureAddressMode.Clamp);
  99. cameraYtexture.SetSize((int)img.Width, (int)img.Height, Graphics.LuminanceFormat, TextureUsage.Dynamic);
  100. cameraYtexture.Name = nameof(cameraYtexture);
  101. Application.ResourceCache.AddManualResource(cameraYtexture);
  102. var viewport = Application.Renderer.GetViewport(0);
  103. var videoRp = new RenderPathCommand(RenderCommandType.Quad);
  104. videoRp.PixelShaderName = (UrhoString)ArkitShader;
  105. videoRp.VertexShaderName = (UrhoString)ArkitShader;
  106. videoRp.SetOutput(0, "viewport");
  107. videoRp.SetTextureName(TextureUnit.Diffuse, cameraYtexture.Name); //sDiffMap
  108. videoRp.SetTextureName(TextureUnit.Normal, cameraUVtexture.Name); //sNormalMap
  109. if (Orientation != UIInterfaceOrientation.Portrait)
  110. videoRp.PixelShaderDefines = new UrhoString("ARKIT_LANDSCAPE");
  111. viewport.RenderPath.InsertCommand(1, videoRp);
  112. var vrp = viewport.RenderPath.GetCommand(1);
  113. vrp->SetShaderParameter("Tx", (float)dt.x0);
  114. vrp->SetShaderParameter("Ty", (float)dt.y0);
  115. vrp->SetShaderParameter("ScaleX", (float)dt.xx);
  116. vrp->SetShaderParameter("ScaleY", (float)dt.yy);
  117. vrp->SetShaderParameter("ScaleYX", (float)dt.yx);
  118. vrp->SetShaderParameter("ScaleXY", (float)dt.xy);
  119. float imageAspect = (float)img.Width / img.Height;
  120. float yoffset;
  121. if (ARConfiguration is ARFaceTrackingConfiguration)
  122. yoffset = 0.013f;
  123. else
  124. yoffset = 64.0f / Math.Max(img.Width, img.Height);
  125. vrp->SetShaderParameter("YOffset", yoffset);
  126. yuvTexturesInited = true;
  127. }
  128. if (yuvTexturesInited)
  129. UpdateBackground(frame);
  130. ARFrame?.Invoke(frame);
  131. // required!
  132. frame.Dispose();
  133. }
  134. public Vector3? HitTest(float screenX = 0.5f, float screenY = 0.5f,
  135. ARHitTestResultType hitTestType = ARHitTestResultType.ExistingPlaneUsingExtent)
  136. => HitTest(ARSession?.CurrentFrame, screenX, screenY, hitTestType);
  137. public Vector3? HitTest(ARFrame frame, float screenX = 0.5f, float screenY = 0.5f,
  138. ARHitTestResultType hitTestType = ARHitTestResultType.ExistingPlaneUsingExtent)
  139. {
  140. var result = frame?.HitTest(new CoreGraphics.CGPoint(screenX, screenY),
  141. ARHitTestResultType.ExistingPlaneUsingExtent)?.FirstOrDefault();
  142. if (result != null && result.Distance > 0.2f)
  143. {
  144. var row = result.WorldTransform.Column3;
  145. return new Vector3(row.X, row.Y, -row.Z);
  146. }
  147. return null;
  148. }
  149. unsafe public void ApplyOpenTkTransform(Node node, OpenTK.NMatrix4 matrix, bool rot = false)
  150. {
  151. Matrix4 urhoTransform = *(Matrix4*)(void*)&matrix;
  152. var rotation = urhoTransform.Rotation;
  153. rotation.Z *= -1;
  154. var pos = urhoTransform.Row3;
  155. node.SetWorldPosition(new Vector3(pos.X, pos.Y, -pos.Z));
  156. node.Rotation = rotation;
  157. if (!rot)
  158. node.Rotate(new Quaternion(0, 0, 90));
  159. }
  160. unsafe void UpdateBackground(ARFrame frame)
  161. {
  162. using (var img = frame.CapturedImage)
  163. {
  164. img.Lock(CoreVideo.CVPixelBufferLock.ReadOnly);
  165. var yPtr = img.BaseAddress;
  166. var uvPtr = img.GetBaseAddress(1);
  167. if (yPtr == IntPtr.Zero || uvPtr == IntPtr.Zero)
  168. return;
  169. int wY = (int)img.Width;
  170. int hY = (int)img.Height;
  171. int wUv = (int)img.GetWidthOfPlane(1);
  172. int hUv = (int)img.GetHeightOfPlane(1);
  173. cameraYtexture.SetData(0, 0, 0, wY, hY, (void*)yPtr);
  174. cameraUVtexture.SetData(0, 0, 0, wUv, hUv, (void*)uvPtr);
  175. img.Unlock(CoreVideo.CVPixelBufferLock.ReadOnly);
  176. }
  177. }
  178. public event Action<ARAnchor[]> DidAddAnchors;
  179. internal void OnDidAddAnchors(ARAnchor[] anchors)
  180. => DidAddAnchors?.Invoke(anchors);
  181. public event Action<ARAnchor[]> DidRemoveAnchors;
  182. internal void OnDidRemoveAnchors(ARAnchor[] anchors)
  183. => DidRemoveAnchors?.Invoke(anchors);
  184. public event Action<ARAnchor[]> DidUpdateAnchors;
  185. internal void OnDidUpdateAnchors(ARAnchor[] anchors)
  186. => DidUpdateAnchors?.Invoke(anchors);
  187. public event Action<CMSampleBuffer> DidOutputAudioSampleBuffer;
  188. internal void OnDidOutputAudioSampleBuffer(CMSampleBuffer audioSampleBuffer)
  189. => DidOutputAudioSampleBuffer?.Invoke(audioSampleBuffer);
  190. public event Action<Foundation.NSError> DidFail;
  191. internal void OnDidFail(Foundation.NSError error)
  192. => DidFail?.Invoke(error);
  193. public event Action WasInterrupted;
  194. internal void OnWasInterrupted()
  195. => WasInterrupted?.Invoke();
  196. public event Action InterruptionEnded;
  197. internal void OnInterruptionEnded()
  198. => InterruptionEnded?.Invoke();
  199. public event Action<ARCamera> CameraDidChangeTrackingState;
  200. internal void OnCameraDidChangeTrackingState(ARCamera camera)
  201. => CameraDidChangeTrackingState?.Invoke(camera);
  202. }
  203. class UrhoARSessionDelegate : ARSessionDelegate
  204. {
  205. WeakReference<ARKitComponent> arkit;
  206. bool runFrames;
  207. public UrhoARSessionDelegate(ARKitComponent arkit, bool runFrames)
  208. {
  209. this.runFrames = runFrames;
  210. this.arkit = new WeakReference<ARKitComponent>(arkit);
  211. }
  212. public override void CameraDidChangeTrackingState(ARSession session, ARCamera camera)
  213. {
  214. if (arkit.TryGetTarget(out var ap) && ap.Application.IsActive)
  215. Urho.Application.InvokeOnMain(() => ap.OnCameraDidChangeTrackingState(camera));
  216. }
  217. public override void DidUpdateFrame(ARSession session, ARFrame frame)
  218. {
  219. if (arkit.TryGetTarget(out var ap))
  220. {
  221. var app = ap.Application;
  222. if (!app.IsActive)
  223. return;
  224. Urho.Application.InvokeOnMain(() => ap.ProcessARFrame(session, frame));
  225. if (runFrames)
  226. {
  227. app.Engine.RunFrame();
  228. }
  229. }
  230. }
  231. public override void DidOutputAudioSampleBuffer(ARSession session, CMSampleBuffer audioSampleBuffer)
  232. {
  233. if (arkit.TryGetTarget(out var ap) && ap.Application.IsActive)
  234. Urho.Application.InvokeOnMain(() => ap.OnDidOutputAudioSampleBuffer(audioSampleBuffer));
  235. }
  236. public override void DidFail(ARSession session, Foundation.NSError error)
  237. {
  238. if (arkit.TryGetTarget(out var ap) && ap.Application.IsActive)
  239. Urho.Application.InvokeOnMain(() => ap.OnDidFail(error));
  240. }
  241. public override void WasInterrupted(ARSession session)
  242. {
  243. if (arkit.TryGetTarget(out var ap) && ap.Application.IsActive)
  244. Urho.Application.InvokeOnMain(() => ap.OnWasInterrupted());
  245. }
  246. public override void InterruptionEnded(ARSession session)
  247. {
  248. if (arkit.TryGetTarget(out var ap) && ap.Application.IsActive)
  249. Urho.Application.InvokeOnMain(() => ap.OnInterruptionEnded());
  250. }
  251. public override void DidAddAnchors(ARSession session, ARAnchor[] anchors)
  252. {
  253. if (arkit.TryGetTarget(out var ap) && ap.Application.IsActive)
  254. Urho.Application.InvokeOnMain(() => ap.OnDidAddAnchors(anchors));
  255. }
  256. public override void DidRemoveAnchors(ARSession session, ARAnchor[] anchors)
  257. {
  258. if (arkit.TryGetTarget(out var ap) && ap.Application.IsActive)
  259. Urho.Application.InvokeOnMain(() => ap.OnDidRemoveAnchors(anchors));
  260. }
  261. public override void DidUpdateAnchors(ARSession session, ARAnchor[] anchors)
  262. {
  263. if (arkit.TryGetTarget(out var ap) && ap.Application.IsActive)
  264. Urho.Application.InvokeOnMain(() => ap.OnDidUpdateAnchors(anchors));
  265. }
  266. }
  267. }