上文說到AR的一些基礎(chǔ)內(nèi)容,這篇我們就來把這個demo走一遍,在做的過程中蝇闭,補(bǔ)充一些ARKit里常用的方法,這樣應(yīng)該會容易理解一些硬毕。
1呻引、Demo的基本思路
用ARKit追蹤地板的平面。取首次追蹤到的平面作為參考平面吐咳,此時這個平面與地面大致是重合的(這里讀者也可以自己通過一些方法提高精準(zhǔn)度逻悠,筆者就不再繼續(xù)深入了)。
取現(xiàn)實(shí)中的地面上的點(diǎn)韭脊,得到它在我們的平面上的坐標(biāo)童谒,通過坐標(biāo)計算距離。
2乾蓬、追蹤平面
在上一篇demo上(沒看上一篇的可以不看直接創(chuàng)建一個項(xiàng)目惠啄,template選argumented reality app就好了)繼續(xù)。
我們先在viewDidLoad里設(shè)置一下debugOptions任内,作用是顯示點(diǎn)位撵渡,方便后面看追蹤情況。
self.sceneView.debugOptions = ARSCNDebugOptionShowFeaturePoints;//顯示追蹤到的點(diǎn)位
追蹤錨點(diǎn)的代理方法:- (void)renderer:(id)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor;死嗦,沒有設(shè)置代理的先設(shè)置一下代理
#pragma mark - ARSCNViewDelegate
//添加節(jié)點(diǎn)時候調(diào)用(當(dāng)開啟平地捕捉模式之后趋距,如果捕捉到平地,ARKit會自動添加一個平地節(jié)點(diǎn))
-(void)renderer:(id)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
if ([anchor isMemberOfClass:[ARAnchor class]]) {
NSLog(@"平地捕捉");
}
// 添加一個3D平面模型越除,ARKit只有捕捉能力节腐,錨點(diǎn)只是一個空間位置,要想更加清楚看到這個空間摘盆,我們需要給空間添加一個平地的3D模型來渲染他
ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
//放一個box翼雀,長寬為錨點(diǎn)平面的長寬
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];
//為了方便看,在錨點(diǎn)處放一個“坐標(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://www.reibang.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這個,并且關(guān)掉追蹤类垦。
- (NSArray*)hitTest:(CGPoint)point types:(ARHitTestResultType)types;此方法會返回空值狈邑,返回空值的原因:
ARHitTestResultTypeExistingPlane:手機(jī)晃動導(dǎo)致無法確定此平面與重力相垂直
ARHitTestResultTypeExistingPlaneUsingExtent:觸碰到點(diǎn)在平面之外,則不返回值蚤认。
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é)點(diǎn)的位置為捕捉到的平地的錨點(diǎn)的中心位置? SceneKit框架中節(jié)點(diǎn)的位置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é)點(diǎn)的位置為捕捉到的平地的錨點(diǎn)的中心位置? SceneKit框架中節(jié)點(diǎn)的位置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:@"上兩點(diǎn)距離:%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é)點(diǎn)的位置為捕捉到的平地的錨點(diǎn)的中心位置? SceneKit框架中節(jié)點(diǎn)的位置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;
}