ARKit:平面追蹤與坐標(biāo)轉(zhuǎn)換

上文說到AR的一些基礎(chǔ)內(nèi)容,這篇我們就來把這個demo走一遍,在做的過程中,補充一些ARKit里常用的方法,這樣應(yīng)該會容易理解一些。

本文demo鏈接

1、Demo的基本思路

用ARKit追蹤地板的平面。取首次追蹤到的平面作為參考平面,此時這個平面與地面大致是重合的(這里讀者也可以自己通過一些方法提高精準(zhǔn)度,筆者就不再繼續(xù)深入了)。

取現(xiàn)實中的地面上的點,得到它在我們的平面上的坐標(biāo),通過坐標(biāo)計算距離。

2、追蹤平面

在上一篇demo上(沒看上一篇的可以不看直接創(chuàng)建一個項目,template選argumented reality app就好了)繼續(xù)。

我們先在viewDidLoad里設(shè)置一下debugOptions,作用是顯示點位,方便后面看追蹤情況。

self.sceneView.debugOptions = ARSCNDebugOptionShowFeaturePoints;//顯示追蹤到的點位

追蹤錨點的代理方法:- (void)renderer:(id)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor;,沒有設(shè)置代理的先設(shè)置一下代理

#pragma mark - ARSCNViewDelegate

//添加節(jié)點時候調(diào)用(當(dāng)開啟平地捕捉模式之后,如果捕捉到平地,ARKit會自動添加一個平地節(jié)點)

-(void)renderer:(id)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{

if ([anchor isMemberOfClass:[ARAnchor class]]) {

NSLog(@"平地捕捉");

}

// 添加一個3D平面模型,ARKit只有捕捉能力,錨點只是一個空間位置,要想更加清楚看到這個空間,我們需要給空間添加一個平地的3D模型來渲染他

ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;

//放一個box,長寬為錨點平面的長寬

SCNBox *plane = [SCNBox boxWithWidth:planeAnchor.extent.x height:0.001 length:planeAnchor.extent.z chamferRadius:0];

SCNMaterial *transparentMaterial = [SCNMaterial new];

transparentMaterial = [self materialNamed:@"tron"];

plane.materials = @[transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial];

SCNNode *planeNode = [SCNNode nodeWithGeometry:plane];

planeNode.position =SCNVector3Make(planeAnchor.center.x, 0, planeAnchor.center.z);

[node addChildNode:planeNode];

//為了方便看,在錨點處放一個“坐標(biāo)系”

SCNTube *tubey = [SCNTube tubeWithInnerRadius:0 outerRadius:0.001 height:5];

tubey.firstMaterial.diffuse.contents = [UIColor blackColor];

SCNNode *tubeNodey = [SCNNode nodeWithGeometry:tubey];

tubeNodey.position = SCNVector3Make(0, 2.5, 0);

[node addChildNode:tubeNodey];

SCNTube *tubex = [SCNTube tubeWithInnerRadius:0 outerRadius:0.001 height:5];

tubex.firstMaterial.diffuse.contents = [UIColor whiteColor];

SCNNode *tubeNodex = [SCNNode nodeWithGeometry:tubex];

tubeNodex.position =? SCNVector3Make(0, 0, 0);

tubeNodex.rotation = SCNVector4Make(1, 0, 0, M_PI/2);

[node addChildNode:tubeNodex];

SCNTube *tubez = [SCNTube tubeWithInnerRadius:0 outerRadius:0.001 height:5];

tubez.firstMaterial.diffuse.contents = [UIColor grayColor];

SCNNode *tubeNodez = [SCNNode nodeWithGeometry:tubez];

tubeNodez.position =? SCNVector3Make(0, 0, 0);

tubeNodez.rotation = SCNVector4Make(0, 0, 1, M_PI/2);

[node addChildNode:tubeNodez];

_zeroNode = [[SCNNode alloc]init];

_zeroNode = node;

//捕捉到第一個平面之后關(guān)閉捕捉

self.arConfiguration.planeDetection = ARPlaneDetectionNone;

[self.sceneView.session runWithConfiguration:self.arConfiguration];

//添加第一根隨著屏幕移動的桿子

[self handleTap:tapGesture];

// [self insertGeometry];

}

//材質(zhì)修改,具體可以看這篇http://m.itdecent.cn/p/07b96c800a1d

- (SCNMaterial *)materialNamed:(NSString *)name {

NSMutableDictionary *materials = [NSMutableDictionary new];

SCNMaterial *mat = materials[name];

if (mat) {

return mat;

}

mat = [SCNMaterial new];

mat.lightingModelName = SCNLightingModelPhysicallyBased;

mat.diffuse.contents = [UIImage imageNamed:@"tron-albedo"];

mat.roughness.contents = [UIImage imageNamed:@"tron-albedo"];

mat.metalness.contents = [UIImage imageNamed:@"tron-albedo"];

mat.normal.contents = [UIImage imageNamed:@"tron-albedo"];

mat.diffuse.wrapS = SCNWrapModeRepeat;

mat.diffuse.wrapT = SCNWrapModeRepeat;

mat.roughness.wrapS = SCNWrapModeRepeat;

mat.roughness.wrapT = SCNWrapModeRepeat;

mat.metalness.wrapS = SCNWrapModeRepeat;

mat.metalness.wrapT = SCNWrapModeRepeat;

mat.normal.wrapS = SCNWrapModeRepeat;

mat.normal.wrapT = SCNWrapModeRepeat;

materials[name] = mat;

return mat;

}


這里是個坑,一定要選ARHitTestResultTypeExistingPlane這個,并且關(guān)掉追蹤。

- (NSArray*)hitTest:(CGPoint)point types:(ARHitTestResultType)types;此方法會返回空值,返回空值的原因:

ARHitTestResultTypeExistingPlane:手機晃動導(dǎo)致無法確定此平面與重力相垂直

ARHitTestResultTypeExistingPlaneUsingExtent:觸碰到點在平面之外,則不返回值。

ARHitTestResultTypeEstimatedHorizontalPlane:正常情況一定會返回值。

#pragma mark - handleTap

- (void) handleTap:(UIGestureRecognizer*)gestureRecognize{CGPoint tapPoint = CGPointMake(redPoint.center.x, redPoint.center.y);NSArray*result = [self.sceneView hitTest:tapPoint types:ARHitTestResultTypeExistingPlane];

if (result.count == 0) {

return;

}

// If there are multiple hits, just pick the closest plane

ARHitTestResult * hitResult = [result firstObject];

SCNTube *calculareTube = [SCNTube tubeWithInnerRadius:0 outerRadius:0.01 height:1];

calculareTube.firstMaterial.diffuse.contents = [UIColor colorWithRed:0 green:255 blue:0 alpha:0.6];

_calculateNode = [SCNNode nodeWithGeometry:calculareTube ];

// 設(shè)置節(jié)點的位置為捕捉到的平地的錨點的中心位置? SceneKit框架中節(jié)點的位置position是一個基于3D坐標(biāo)系的矢量坐標(biāo)SCNVector3Make

_calculateNode.position =? SCNVector3Make(

hitResult.worldTransform.columns[3].x,

hitResult.worldTransform.columns[3].y+0.5,

hitResult.worldTransform.columns[3].z

);

[self.sceneView.scene.rootNode addChildNode:_calculateNode];

if (cubeNumber > 0) {

SCNNode *previousNode;

previousNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-2];

previousNode.geometry.firstMaterial.diffuse.contents = [UIColor greenColor];

if (cubeNumber>1) {

SCNNode *currentNode;

currentNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-3];

currentNode.geometry.firstMaterial.diffuse.contents = [UIColor redColor];

if (cubeNumber>2) {

SCNNode *oldNode;

oldNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-4];

oldNode.geometry.firstMaterial.diffuse.contents = [UIColor whiteColor];

}

[self calculateDistance];

}

}

cubeNumber += 1;

}


//讓透明的桿子隨著攝像頭移動在捕捉到的平面上動起來

#pragma mark - ARSessionDelegate

- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame{

if (cubeNumber == 0)? return;

CGPoint tapPoint = CGPointMake(redPoint.center.x, redPoint.center.y);NSArray*result = [self.sceneView hitTest:tapPoint types:ARHitTestResultTypeExistingPlane];

if (result.count == 0) {

return;

}

// If there are multiple hits, just pick the closest plane

ARHitTestResult * hitResult = [result firstObject];

SCNNode *currentNode;

currentNode = [self.sceneView.scene.rootNode.childNodes lastObject];

// 設(shè)置節(jié)點的位置為捕捉到的平地的錨點的中心位置? SceneKit框架中節(jié)點的位置position是一個基于3D坐標(biāo)系的矢量坐標(biāo)SCNVector3Make

currentNode.position =? SCNVector3Make(

hitResult.worldTransform.columns[3].x,

hitResult.worldTransform.columns[3].y+0.5,

hitResult.worldTransform.columns[3].z

);

}

//計算距離

-(void)calculateDistance{

CGFloat distance;

SCNNode *previousNode;

previousNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-3];

SCNNode *currentNode;

currentNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-2];

distance = sqrt((previousNode.position.x-currentNode.position.x)*(previousNode.position.x-currentNode.position.x)+(previousNode.position.z-currentNode.position.z)*(previousNode.position.z-currentNode.position.z));

distanceLab.text = [NSString stringWithFormat:@"上兩點距離:%f 米",distance];

}

//手勢添加桿子方法

#pragma mark - handleTap

- (void) handleTap:(UIGestureRecognizer*)gestureRecognize{

CGPoint tapPoint = CGPointMake(redPoint.center.x, redPoint.center.y);NSArray*result = [self.sceneView hitTest:tapPoint types:ARHitTestResultTypeExistingPlane];

if (result.count == 0) {

return;

}

// If there are multiple hits, just pick the closest plane

ARHitTestResult * hitResult = [result firstObject];

SCNTube *calculareTube = [SCNTube tubeWithInnerRadius:0 outerRadius:0.01 height:1];

calculareTube.firstMaterial.diffuse.contents = [UIColor colorWithRed:0 green:255 blue:0 alpha:0.6];

_calculateNode = [SCNNode nodeWithGeometry:calculareTube ];

// 設(shè)置節(jié)點的位置為捕捉到的平地的錨點的中心位置? SceneKit框架中節(jié)點的位置position是一個基于3D坐標(biāo)系的矢量坐標(biāo)SCNVector3Make

_calculateNode.position =? SCNVector3Make(

hitResult.worldTransform.columns[3].x,

hitResult.worldTransform.columns[3].y+0.5,

hitResult.worldTransform.columns[3].z

);

[self.sceneView.scene.rootNode addChildNode:_calculateNode];

if (cubeNumber > 0) {

SCNNode *previousNode;

previousNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-2];

previousNode.geometry.firstMaterial.diffuse.contents = [UIColor greenColor];

if (cubeNumber>1) {

SCNNode *currentNode;

currentNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-3];

currentNode.geometry.firstMaterial.diffuse.contents = [UIColor redColor];

if (cubeNumber>2) {

SCNNode *oldNode;

oldNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-4];

oldNode.geometry.firstMaterial.diffuse.contents = [UIColor whiteColor];

}

[self calculateDistance];

}

}

cubeNumber += 1;

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容