上文说到AR的一些基础内容,这篇我们就来把这个demo走一遍,在做的过程中,补充一些ARKit里常用的方法,这样应该会容易理解一些。
本文demo链接
1、Demo的基本思路
用ARKit追踪地板的平面。取首次追踪到的平面作为参考平面,此时这个平面与地面大致是重合的(这里读者也可以自己通过一些方法提高精准度,笔者就不再继续深入了)。
取现实中的地面上的点,得到它在我们的平面上的坐标,通过坐标计算距离。
2、追踪平面
在上一篇demo上(没看上一篇的可以不看直接创建一个项目,template选argumented reality app就好了)继续。
我们先在viewDidLoad里设置一下debugOptions,作用是显示点位,方便后面看追踪情况。
self.sceneView.debugOptions = ARSCNDebugOptionShowFeaturePoints;//显示追踪到的点位
追踪锚点的代理方法:- (void)renderer:(id)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor;,没有设置代理的先设置一下代理
#pragma mark - ARSCNViewDelegate
//添加节点时候调用(当开启平地捕捉模式之后,如果捕捉到平地,ARKit会自动添加一个平地节点)
-(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];
//为了方便看,在锚点处放一个“坐标系”
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;
//捕捉到第一个平面之后关闭捕捉
self.arConfiguration.planeDetection = ARPlaneDetectionNone;
[self.sceneView.session runWithConfiguration:self.arConfiguration];
//添加第一根随着屏幕移动的杆子
[self handleTap:tapGesture];
// [self insertGeometry];
}
//材质修改,具体可以看这篇http://www.jianshu.com/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这个,并且关掉追踪。
- (NSArray*)hitTest:(CGPoint)point types:(ARHitTestResultType)types;此方法会返回空值,返回空值的原因:
ARHitTestResultTypeExistingPlane:手机晃动导致无法确定此平面与重力相垂直
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 ];
// 设置节点的位置为捕捉到的平地的锚点的中心位置 SceneKit框架中节点的位置position是一个基于3D坐标系的矢量坐标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];
// 设置节点的位置为捕捉到的平地的锚点的中心位置 SceneKit框架中节点的位置position是一个基于3D坐标系的矢量坐标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 ];
// 设置节点的位置为捕捉到的平地的锚点的中心位置 SceneKit框架中节点的位置position是一个基于3D坐标系的矢量坐标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;
}