3

I'm trying to integrate this feature https://github.com/heinrisch/faceid-to-stl into my react native app. The repo is written in swift and I basically want to display the ViewController class into my react native app but I'm not sure how to. Wanted to try this out first and then eventually I want to integrate the StandardCyborg SDK https://standardcyborg.com/ instead to do 3D face scanning for a React Native app.

I've tried multiple posts about bridging native modules and functions and I've been able to do that. But I'm thinking ViewControllers are a bit different since they're not UI Components? Or are they? Very new to bridging native modules and not familiar with swift. Any help is much appreciated.

ViewController.m

#import <Foundation/Foundation.h>

#import <React/RCTBridgeModule.h>
#import <React/RCTViewManager.h>
#import <React/RCTEventEmitter.h>

@interface RCT_EXTERN_MODULE(Mask, NSObject)
RCT_EXTERN_METHOD(init)
RCT_EXTERN_METHOD(init?)
RCT_EXTERN_METHOD(update)
@end

@interface RCT_EXTERN_MODULE(ViewController, NSObject)
RCT_EXTERN_METHOD(viewDidLoad)
RCT_EXTERN_METHOD(renderer)
RCT_EXTERN_METHOD(renderer)
RCT_EXTERN_METHOD(startSession)
RCT_EXTERN_METHOD(capture)
RCT_EXTERN_METHOD(presentShareSheet)
RCT_EXTERN_METHOD(createSTL)
RCT_EXTERN_METHOD(generateURL)
@end

oxygen-Bridging-Header.h


#import <React/RCTBridgeModule.h>


AppDelegate.m

#import "AppDelegate.h"

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"oxygen"
                                            initialProperties:nil];

  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  return YES;
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

@end

AppDelegate.h


#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>

@property (nonatomic, strong) UIWindow *window;

@end

ViewController.swift

@objc(ViewController)
class ViewController: UIViewController, ARSCNViewDelegate {

  @objc @IBOutlet var sceneView: ARSCNView!
  @objc private var isPresentingShareSheet = false
  @objc private var mask: Mask?
  @objc private var lastFaceAnchor: ARFaceAnchor?

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return true
  }

  @objc
  override func viewDidLoad() {
    print("ViewController loaded...")
    super.viewDidLoad()

    sceneView.delegate = self
    startSession()

    let button = UIButton(type: .custom)
    button.setTitle("Capture", for: .normal)
    button.tintColor = .black
    button.titleLabel?.textColor = .black
    button.backgroundColor = .lightGray
    button.addTarget(self, action: #selector(capture), for: .touchUpInside)
    button.layer.cornerRadius = 12
    view.addSubview(button)

    button.translatesAutoresizingMaskIntoConstraints = false
    let constraints: [NSLayoutConstraint] = [
      button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      button.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -40),
      button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
      button.heightAnchor.constraint(equalToConstant: 55)
    ]
    view.addConstraints(constraints)
  }

  @objc
  func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    //        let device = sceneView.device!
    //        let maskGeometry = ARSCNFaceGeometry(device: device)!
    //        mask = Mask(geometry: maskGeometry)
    let device = MTLCreateSystemDefaultDevice()
    let maskGeometry = ARSCNFaceGeometry(device: device!)!
    mask = Mask(geometry: maskGeometry)
    node.addChildNode(mask!)

  }

  @objc
  func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let faceAnchor = anchor as? ARFaceAnchor else { return }
    mask?.update(withFaceAnchor: faceAnchor)

    if let faceAnchor = anchor as? ARFaceAnchor {
      lastFaceAnchor = faceAnchor
    }
  }

  @objc
  private func startSession() {
    sceneView.scene.rootNode.childNodes.forEach {
      $0.removeFromParentNode()
    }

    let configuration = ARFaceTrackingConfiguration()
    configuration.isLightEstimationEnabled = true
    sceneView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
  }

  @objc
  private func capture() {
    guard let faceAnchor = lastFaceAnchor else {
      return
    }

    let data = createSTL(from: faceAnchor)
    let url = generateURL(for: data)
    presentShareSheet(with: url)
  }

  @objc
  private func createSTL(from faceAnchor: ARFaceAnchor) -> Data {

    let mapped = faceAnchor.geometry.triangleIndices.map { i in
      return faceAnchor.geometry.vertices[Int(i)]
    }

    var out: [String] = ["solid face"]
    mapped.enumerated().forEach { i, vertex in
      if i % 3 == 0 {
        out.append("facet normal 0 0 0")
        out.append("\touter loop")
      }

      out.append("\t\tvertex \(vertex.x) \(vertex.y) \(vertex.z)")

      if i % 3 == 2 {
        out.append("\tendloop")
      }
    }

    out.append("endsolid face")

    let file = out.joined(separator: "\n")
    let data = file.data(using: .ascii)!
    return data
  }

  @objc
  private func generateURL(for data: Data) -> URL {
    let url = URL(fileURLWithPath: NSTemporaryDirectory() + "face.stl")
    try! data.write(to: url)
    return url
  }

  @objc
  private func presentShareSheet(with url: URL) {
    self.isPresentingShareSheet = true
    DispatchQueue.main.async {
      let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
      activityViewController.completionWithItemsHandler = { _, _, _, _ in
        self.isPresentingShareSheet = false
      }
      self.present(activityViewController, animated: true, completion: nil)
    }
  }
}
6
  • that's only for x, xs, xr devices. you can't use it on different devices. Commented Jun 5, 2019 at 21:35
  • I'm writing it for an iPhone X, just that I'm not familiar with Swift and decided i'd do it in ReactNative. I was just wondering if it's possible to display the ViewController, but do you suggest I just try learning Swift instead? Commented Jun 5, 2019 at 21:41
  • stackoverflow.com/questions/45741903/… Commented Jun 5, 2019 at 21:50
  • Was looking at that, but had a bit of trouble. I'll try again, thanks though! Commented Jun 5, 2019 at 22:00
  • 1
    @fpang did you get solution for this? Commented Jan 10, 2020 at 15:18

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.