Danny의 iOS 컨닝페이퍼
article thumbnail

시작

오늘은 SceneKit으로 카메라를 통한 줄자를 만들어 보겠습니다.

 

간단히 설명할 예정이라 자세한 내용은 이전 글을 참고하시길 바랍니다.

SceneKit의 사용법 (1) - 정육면체와 달을 만들어 보자

SceneKit의 사용법 (2) - 주사위 만들기

 

화면의 위치 얻기

touchesBegan 메서드를 통하여 터치한 화면(2D 공간)에서 실제 카메라에 비치는(3D 공간) 좌표를 구해봅시다.

 

raycastQuery를 통하여 터치 시 그 공간 좌표정보를 요청한 뒤,

raycast로 만든 쿼리값(좌표정보 요청)을 넣어준 뒤 결괏값으로 변환시켜서 좌표 정보를 얻을 수 있습니다.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    resetDot()
    
    guard let touch = touches.first else { return }
    
    // 터치되는 위치 얻기
    let touchLocation = touch.location(in: sceneView)
    
    // 2D공간(화면)을 3D로 좌표를 계산하여 변환시켜 준다.
    guard let query = sceneView.raycastQuery(from: touchLocation,
                                             allowing: .existingPlaneGeometry,
                                             alignment: .any) else { return }
    
    // 터치시 3D공간의 위치 정보 결과들 (return = [ARRaycastResult])
    let hitResults = sceneView.session.raycast(query)
    
    // 터치시 3D공간의 위치결과
    guard let hitResult = hitResults.first else { return }
    
    // 점을 추가하기
    addDot(at: hitResult)
}

여기서 resetDot() , addDot(at:)는 코드 정리를 위해 따로 묶어놓았습니다. 바로 밑에서 설명드리겠습니다.

 

점 만들기

위에 공간 좌표를 얻었으면 이제 터치한 지점에 점을 생성시켜 봅시다.

 

터치한 위치(hitResult)의 리턴값이 ARRaycastResult 이므로 이 위치를 받아서 점을 생성시킵니다.

 

ARKit의 형상 생성법을 모르신다면 SceneKit의 사용법 (1) - 정육면체와 달을 만들어 보자를 참고하세요

    func addDot(at location: ARRaycastResult) {
        
        let dotGeometry = SCNSphere(radius: 0.002)
        
        let material = SCNMaterial()
        material.diffuse.contents = UIColor.red
        
        dotGeometry.materials = [material]
        
        let dotNode = SCNNode()
        dotNode.geometry = dotGeometry
        dotNode.position = SCNVector3(location.worldTransform.columns.3.x,
                                      location.worldTransform.columns.3.y,
                                      location.worldTransform.columns.3.z)
        
        sceneView.scene.rootNode.addChildNode(dotNode)
        
        // 나중에 초기화 시킬 때 사용하려고 전역변수에 담았습니다.
        dotNodes.append(dotNode)
        
        // 점을 두번 찍으면 거리를 계산하기.
        if dotNodes.count >= 2 {
            calculate()
        }
    }

또한 계산 및 리셋을 위해 전역변수를 만들어 줍시다.

var dotNodes = [SCNNode]()

 

계산하기

가장 중요한 두 점 사이를 계산해주는 함수를 만들어야 합니다.

 

그전에 우리는 수학 공부를 간단히 해봅시다.......

 

자 다들 피타고라스의 정리에 대해서 들어보셨을 겁니다.

2차원으로 된 두 점 사이의 거리는 아마 다들 구하실 수 있을 거예요. 

 

그런데 우리는 3차원의 두 점 사이의 거리를 구해야 됩니다. 점의 생성 좌표가 3차원이기 때문이죠.

 

그림을 보면서 이해해 봅시다.

3차원 공간의 두 점

 

피타고라스의 정리를 생각하며 각각의 위치를 통해 가상의 정육면체를 생성해 줍니다.

이렇게 보면 📐 ABQ가 직각삼각형이니 우리는 변 AB의 길이, 즉 두 점 사이의 거리를 구할 수 있습니다!

 

먼저 📐 APQ를 통하여 변 AQ를 구해봅시다. 

변 AQ = √ ( (변 PQ)² + (변 AP)² )

변 AQ = √ ( (x₂ - x₁)² + (y₂ - y₁)² )

 

 📐 ABQ의 밑변인 변 AQ를 구했으니 높이인 변 BQ를 갖고 바로 빗변인 변 AB를 구할 수 있습니다.

변 AB = √ ( (변 AQ)² + (변 BQ)² )

변 AB = √ ( (x₂ - x₁)² + (y₂ - y₁)² + (z₂ - z₁)² )

 

이렇게 이제 우리는 3D공간의 두 점 사이의 거리를 구할 수 있게 됐습니다.

 

바로 코드에 적용해 봅시다. 

 

일단 전역변수로 첫 번째 위치와 두 번째 위치를 갖고 온 뒤

제곱근(루트, √)sqrt 와  제곱 함수(pow) 를 사용하여 두 점 사이를 구해봅시다.

func calculate() {
    let start = dotNodes[0]
    let end = dotNodes[1]
    
    // 그림 참고. 월드 좌표계의 두 점 사이의 거리 구하기
    // sqrt 제곱근(루트, √) 만드는 함수 , pow 제곱하기
    let distance = sqrtf(powf(end.position.x - start.position.x, 2) +    // 변 PQ
                         powf(end.position.y - start.position.y, 2) +    // 변 AP
                         powf(end.position.z - start.position.z, 2))     // 변 BQ
    
    // cm로 변형
    let distanceUnitCm = String(format: "%.2f", distance * 100)
    
    updateText(text: distanceUnitCm + "Cm", atPosition: start.position)
}

 

입체 글자 생성하기

ARKit의 형상 생성법을 모르신다면 SceneKit의 사용법 (1) - 정육면체와 달을 만들어 보자를 참고하세요

 

SCNText를 생성할 때는 Material을 따로 만들지 않아도 됩니다.

func updateText(text: String, atPosition position: SCNVector3) {
    // 업데이트 될때 마다 텍스트 지우기
    textNode.removeFromParentNode()
    
    // 입체감 있는 텍스트를 생성
    let textGeometry = SCNText(string: text, extrusionDepth: 1.0)
    
    // material을 따로 만들지 않아도 된다.
    textGeometry.firstMaterial?.diffuse.contents = UIColor.red
    
    textNode = SCNNode(geometry: textGeometry)
    
    textNode.position = SCNVector3(position.x + 0.05, position.y  , position.z - 0.25)
    
    // 기본 단위가 미터라서 스케일을 줄여줌.
    textNode.scale = SCNVector3(0.005, 0.005, 0.005)
    
    sceneView.scene.rootNode.addChildNode(textNode)
}

여기도 마찬가지로 리셋을 위해서 전역변수를 만들어 줬습니다.

var textNode = SCNNode()

 

리셋시키기

마지막입니다. 지금까진 터치 시 점을 생성만 계속하게 되죠.

 

그러므로 조건을 걸어서 점이 2개 이상이면 리셋을 시켜줄 겁니다.

func resetDot() {
    if dotNodes.count >= 2 {
        // node에서 점을 지우기
        dotNodes.forEach { $0.removeFromParentNode() }
        
        // 계산을 위해 다시 초기화
        dotNodes = [SCNNode]()
    }
}

 

완성

청소를 안 해서 그런지 맥북이 반짝 거리네요...

제 맥북의 길이는 30.4cm인데 테스터 결과 30.49cm!!!

거리 정확도가 꽤나 높다는 것을 알 수 있었습니다.

전체 코드를 보고 싶다면 Github 를 참고하세요.

 

 

ScnenKit의 다른 예제를 보고 싶다면 아래 링크를 참고하세요

 

[iOS/Swift] SceneKit의 사용법 (1) - 정육면체와 달을 만들어 보자

시작 AR 주사위 던지는 앱을 만들기 앞서 이해를 돕기 위해 들어가기 앞서 간단한 Cube(정육면체)와 Shpere(구)를 만들어 봅시다. 사용법 iOS -> Augmented Reality App 선택 Content Technology : SceneKit을 선택해

ios-daniel-yang.tistory.com

 

[iOS/Swift] SceneKit의 사용법 (2) - 주사위 만들기

시작 오늘은 SceneKit으로 주사위 생성 및 굴리기를 해봅시다. 순서는 이전에 만든 Cube와 달 만들기와 거의 동일하니 못 보셨다면 여기를 참고하세요. 바로 시작하겠습니다. 주사위로 사용할 모델

ios-daniel-yang.tistory.com

 

[iOS/Swift] SceneKit의 사용법 (4) - 이미지를 인식하여 3D형상 만들기

시작 오늘은 SceneKit으로 이미지를 인식하여 그 위에 캐릭터를 올려볼 예정입니다. 간단히 설명할 예정이라 자세한 내용은 이전 글을 참고하시길 바랍니다. SceneKit의 사용법 (1) - 정육면체와 달을

ios-daniel-yang.tistory.com

 

참고

 

iOS & Swift - The Complete iOS App Development Bootcamp

From Beginner to iOS App Developer with Just One Course! Fully Updated with a Comprehensive Module Dedicated to SwiftUI!

www.udemy.com

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!