iOS11 AR情境中關於3D模型的觸碰檢測,ios11ar
最新 iOS11中的AR特別火爆,自己也到網上找了幾個Demo把玩了下,核心代碼無非以下:
//AR視圖:展示3D介面
@property(nonatomic,strong)ARSCNView *arSCNView;
添加模型方法一:
// Create a new scene
SCNScene *scene = [SCNScene sceneNamed:@"art.scnassets/ship.scn"];
// Set the scene to the view
self.arSCNView.scene = scene;
添加模型方法二:
//1.使用情境載入scn檔案(scn格式檔案是一個基於3D建模的檔案,使用3DMax軟體可以建立,這裡系統有一個預設的3D飛機)--------在右側我添加了許多3D模型,只需要替換檔案名稱即可
SCNScene *scene = [SCNScene sceneNamed:@"art.scnassets/ship.scn"];//@"Models.scnassets/chair/chair.scn"];
//2.擷取飛機節點(一個情境會有多個節點,此處我們唯寫,飛機節點則預設是情境子節點的第一個)
//所有的情境有且只有一個根節點,其他所有節點都是根節點的子節點
SCNNode *shipNode = scene.rootNode.childNodes[0];
self.planeNode = shipNode;
//飛機比較大,釋放縮放一下並且調整位置讓其在螢幕中間
shipNode.scale = SCNVector3Make(0.5, 0.5, 0.5);
shipNode.position = SCNVector3Make(0, -15,-15);
;
//一個飛機的3D建模不是一氣呵成的,可能會有很多個子節點拼接,所以裡面的子節點也要一起改,否則上面的修改會無效
for (SCNNode *node in shipNode.childNodes) {
node.scale = SCNVector3Make(0.5, 0.5, 0.5);
node.position = SCNVector3Make(0, -15,-15);
}
//3.將飛機節點添加到當前螢幕中
[self.arSCNView.scene.rootNode addChildNode:shipNode];
//環境搭建代碼見文後 結尾分割線 下
========================開頭分割線====================
最初想到的當然是通過添加手勢看能否湊效,但是如果向ARSCNView添加,有點兒盲目做功,因為ARSCNView上不全是所求的3D模型,不管有沒有點擊到模型,手勢方法都會出發;再觀察SCNScene, SCNScene繼承自NSObject,觀察了API又陷入了死胡同;返回來看ARSCNView有個方法:
/*!
@method hitTest:options:
@abstract Returns an array of SCNHitTestResult for each node that contains a specified point.
@param point A point in the coordinate system of the receiver.
@param options Optional parameters (see the "Hit test options" group for the available options).
*/
- (NSArray<SCNHitTestResult *> *)hitTest:(CGPoint)point options:(nullable NSDictionary<SCNHitTestOption, id> *)options;
該方法會返回一個SCNHitTestResult數組,這個數組中每個元素的node都包含了指定的點(CGPoint)
打個比方:ARSCNView就是個多層合成木板,手指的每次點擊,都好像一根針穿透模板,該方法會反回由針穿過的所有點(node)組成的一餓數組,每個點其實都包含了你手指點擊的位置(CGPoint),這樣我們就可以通過便利每個數組中每個SCNHitTestResult的node,看哪個node有父node,並且找到node的name和3D模型的根節點name做對比,可以找到那就是點擊到了3D模型;
代碼如下:
SCNNode *vaseNode = scene.rootNode.childNodes[0];
vaseNode.name = @"Virtual object root node";//很重要,就靠name做對比
//4.設定花瓶節點的位置為捕捉到的平地的位置,如果不設定,則預設為原點位置,也就是相機位置
vaseNode.position = SCNVector3Make(planeAnchor.center.x, 0, planeAnchor.center.z);
//5.將花瓶節點添加到當前螢幕中
//!!!此處一定要注意:花瓶節點是添加到代理捕捉到的節點中,而不是AR試圖的根節點。因為捕捉到的平地錨點是一個本地座標系,而不是全局座標系
[node addChildNode:vaseNode];
// 系統方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (self.arType == ARTypePlane&&self.rootNode) {
self.currentTouches = touches;
UITouch *touch = [self.currentTouches anyObject];
CGPoint tapPoint = [touch locationInView:self.arSCNView];//該點就是手指的點擊位置
NSDictionary *hitTestOptions = [NSDictionary dictionaryWithObjectsAndKeys:@(true),SCNHitTestBoundingBoxOnlyKey, nil];
NSArray<SCNHitTestResult *> * results= [self.arSCNView hitTest:tapPoint options:hitTestOptions];
for (SCNHitTestResult *res in results) {//遍曆所有的返回結果中的node
if ([self isNodePartOfVirtualObject:res.node]) {
[self doSomeThing];
break;
}
}
}
}
//上溯找尋指定的node
-(BOOL) isNodePartOfVirtualObject:(SCNNode*)node {
if ([@"Virtual object root node" isEqualToString:node.name]) {
return true;
}
if (node.parentNode != nil) {
return [self isNodePartOfVirtualObject:node.parentNode];
}
return false;
}
===========結尾分割線==============
#pragma mark -搭建ARKit環境
//懶載入會話追蹤配置
- (ARSessionConfiguration *)arSessionConfiguration
{
if (_arSessionConfiguration != nil) {
return _arSessionConfiguration;
}
//1.建立世界追蹤會話配置(使用ARWorldTrackingSessionConfiguration效果更加好),需要A9晶片支援
ARWorldTrackingSessionConfiguration *configuration = [[ARWorldTrackingSessionConfiguration alloc] init];
//2.設定追蹤方向(追蹤平面,後面會用到)
configuration.planeDetection = ARPlaneDetectionHorizontal;
_arSessionConfiguration = configuration;
//3.自適應燈光(相機從暗到強光快速過渡效果會平緩一些)
_arSessionConfiguration.lightEstimationEnabled = YES;
return _arSessionConfiguration;
}
//懶載入拍攝會話
- (ARSession *)arSession
{
if(_arSession != nil)
{
return _arSession;
}
//1.建立會話
_arSession = [[ARSession alloc] init];
_arSession.delegate = self;
//2返回會話
return _arSession;
}
//建立AR視圖
- (ARSCNView *)arSCNView
{
if (_arSCNView != nil) {
return _arSCNView;
}
//1.建立AR視圖
_arSCNView = [[ARSCNView alloc] initWithFrame:self.view.bounds];
//2.設定代理 捕捉到平地會在代理回調中返回
_arSCNView.delegate = self;
//2.設定視圖會話
_arSCNView.session = self.arSession;
//3.自動重新整理燈光(3D遊戲用到,此處可忽略)
_arSCNView.automaticallyUpdatesLighting = YES;
return _arSCNView;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//1.將AR視圖添加到當前視圖
[self.view addSubview:self.arSCNView];
//2.開啟AR會話(此時相機開始工作)
[self.arSession runWithConfiguration:self.arSessionConfiguration];
}