我正在尝试将此功能https://github.com/heinrisch/faceid-to-stl集成到我的反应本机应用程序中。repo 是用 swift 编写的,我基本上想将 ViewController 类显示到我的 react native 应用程序中,但我不知道该怎么做。想先尝试一下,然后最终我想集成 StandardCyborg SDK https://standardcyborg.com/而不是为 React Native 应用程序进行 3D 面部扫描。
我已经尝试了多篇关于桥接本机模块和功能的帖子,并且我已经能够做到这一点。但我认为 ViewControllers 有点不同,因为它们不是 UI 组件?还是他们?对桥接本机模块非常陌生,不熟悉 swift。任何帮助深表感谢。
视图控制器.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
氧桥接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)
}
}
}