İçeriğe geç

ARKit ile zemin döşeme

Merhabalar bu yazımda ARKit ile en önemli konulardan olan dikey olarak zemini algılayıp, buraya texture nasıl ekleriz bunu göstereceğim.

İOS 11 ile gelen ARKit üzerinde başarılı görüntü işleme algoritmaları mevcut. Bu sayede gerçek zamanlı olarak zemin olup olmadığını algılayabilmekte. Bu sayede bizde zemine istediğimiz texture ekleyebilir, bu zemine static bir fizik ekleyerek collision görevi görmesini sağlayabiliriz. Daha sonrada bu zemin üzerinde çeşitli aksiyonlar,oyunlar vs aklınıza ne gelirse yazabilirsiniz.

import UIKit
import SceneKit
import ARKit

enum butState: String {
    case start = "Tap start AR"
    case stop = "Stop tracking"
    case select = "Tap plane select"
    case reset = "Reset"
}

class ViewController: UIViewController, ARSCNViewDelegate {
    
    @IBOutlet var sceneView: ARSCNView!
    @IBOutlet weak var menuButton: UIButton!
    @IBOutlet weak var statusLabel: UILabel!
    
    var arState = butState.start
    var scene = SCNScene()
    var configuration = ARWorldTrackingConfiguration()
    
    var anchors = [ARAnchor]()
    var nodes = [SCNNode]()
    var planeNodesCount = 0
    var planeHeight: CGFloat = 0.01
    var disableTracking = false
    var isPlaneSelected = false

    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        sceneView.delegate = self
        
        sceneView.showsStatistics = true
        
        self.sceneView.scene = scene
        self.sceneView.autoenablesDefaultLighting = true
        
        self.sceneView.debugOptions  = [.showConstraints, .showLightExtents]
        self.sceneView.showsStatistics = false
        self.sceneView.automaticallyUpdatesLighting = true
        menuButton.setTitle(arState.rawValue , for: .normal)
        
    }
    
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        setSessionConfiguration(pd: .horizontal, runOPtions: .resetTracking)
        
    }
    
    func setSessionConfiguration(pd : ARWorldTrackingConfiguration.PlaneDetection,
                                 runOPtions: ARSession.RunOptions) {
  
        configuration.planeDetection = pd
        sceneView.session.run(configuration, options: runOPtions)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        sceneView.session.pause()
    }
    
    // DELEGATE
    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        guard disableTracking == false else {
            return nil
        }
        var node:  SCNNode?
        if let planeAnchor = anchor as? ARPlaneAnchor {
            
            node = SCNNode()
            let planeGeometry = SCNBox(width: CGFloat(planeAnchor.extent.x), height: planeHeight, length: CGFloat(planeAnchor.extent.z), chamferRadius: 0.0)
            planeGeometry.firstMaterial?.diffuse.contents = #imageLiteral(resourceName: "tron")
            planeGeometry.firstMaterial?.specular.contents = UIColor.white
            let planeNode = SCNNode(geometry: planeGeometry)
            planeNode.position = SCNVector3Make(planeAnchor.center.x, Float(planeHeight / 2), planeAnchor.center.z)
               //   planeNode.transform = SCNMatrix4MakeRotation(Float(-CGFloat.pi/2), 1, 0, 0) vertical node
            node?.addChildNode(planeNode)
            anchors.append(planeAnchor)
            
        } else {
            
            print("not plane anchor \(anchor)")
        }
        return node
    }
    
    
    public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        planeNodesCount += 1
        if node.childNodes.count > 0 && planeNodesCount % 2 == 0 {
            node.childNodes[0].geometry?.firstMaterial?.diffuse.contents = UIColor.yellow
        }
    }
    
    
    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard disableTracking == false else {
            return
        }
        
        if let planeAnchor = anchor as? ARPlaneAnchor {
            if anchors.contains(planeAnchor) {
                if node.childNodes.count > 0 {
                    let planeNode = node.childNodes.first!
                    planeNode.position = SCNVector3Make(planeAnchor.center.x, Float(planeHeight / 2), planeAnchor.center.z)
                    if let plane = planeNode.geometry as? SCNBox {
                        plane.width = CGFloat(planeAnchor.extent.x)
                        plane.length = CGFloat(planeAnchor.extent.z)
                        plane.height = planeHeight
                    }
                }
            }
        }
    }
    
    
    func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) {
        print("remove node delegate called")
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        let touch = touches.first!
        let location = touch.location(in: sceneView)
        
        if arState == .select {
            selectExistinPlane(location: location)
        }
        
    }
    
    func selectExistinPlane(location: CGPoint) {
        let hitResults = sceneView.hitTest(location, types: .existingPlaneUsingExtent)
        if hitResults.count > 0 {
            let result: ARHitTestResult = hitResults.first!
            if let planeAnchor = result.anchor as? ARPlaneAnchor {
                for var index in 0...anchors.count - 1 {
                    if anchors[index].identifier != planeAnchor.identifier {
                        sceneView.node(for: anchors[index])?.removeFromParentNode()
                    }
                    index += 1
                }
                anchors = [planeAnchor]
                setTexture(node: sceneView.node(for: anchors[0])!)
            }
        }
    }
    
    func reset() {
        
        if anchors.count > 0 {
            for index in 0...anchors.count - 1 {
                sceneView.node(for: anchors[index])?.removeFromParentNode()
            }
            anchors.removeAll()
        }
        
        for node in sceneView.scene.rootNode.childNodes {
            node.removeFromParentNode()
        }
        
    }
    
    func setTexture(node: SCNNode) {
        if let geometryNode = node.childNodes.first {
            if node.childNodes.count > 0 {
                geometryNode.geometry?.firstMaterial?.diffuse.contents = #imageLiteral(resourceName: "texttt")
                geometryNode.geometry?.firstMaterial?.locksAmbientWithDiffuse = true
                geometryNode.geometry?.firstMaterial?.diffuse.wrapS = SCNWrapMode.repeat
                geometryNode.geometry?.firstMaterial?.diffuse.wrapT = SCNWrapMode.repeat
                geometryNode.geometry?.firstMaterial?.diffuse.mipFilter = SCNFilterMode.linear
            }
            arState = butState.reset
            menuButton.setTitle(butState.reset.rawValue, for: .normal)
        }
    }
    
    func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
        switch camera.trackingState {
        case .normal:
            statusLabel.text = "Normal"
        case .notAvailable:
            statusLabel.text = "Not Available"
        case .limited(let reason):
            statusLabel.text = "Limited with reason: "
            switch reason {
            case .excessiveMotion:
                statusLabel.text = statusLabel.text! + "excessive camera movement"
            case .insufficientFeatures:
                statusLabel.text = statusLabel.text! + "insufficient features"
            case .initializing:
                statusLabel.text = statusLabel.text! + "init features"
            }
        }
    }
    
    @IBAction func menuButtonTapped(_ sender: Any) {
        switch arState {
        case .start:
            disableTracking = false
            setSessionConfiguration(pd: ARWorldTrackingConfiguration.PlaneDetection.horizontal, runOPtions: ARSession.RunOptions.resetTracking)
            arState = .stop
            menuButton.setTitle(butState.stop.rawValue, for: .normal)
            
        case .stop:
            disableTracking = true
            arState = butState.select
            menuButton.setTitle(butState.select.rawValue, for: .normal)
            
        case .select:
            arState = butState.reset
            menuButton.setTitle(butState.reset.rawValue, for: .normal)
            break
        case .reset:
            disableTracking = false
            arState = .start
            menuButton.setTitle(butState.start.rawValue, for: .normal)
            reset()
            configuration = ARWorldTrackingConfiguration()
            break
        }
    }
   
    override var prefersStatusBarHidden: Bool {
        return true
    }
    
}//

Örnekte önemli olan noktalar ARKit üzerinde kullandığımız renderer fonksiyonları. Bu fonksiyonlar gerçek zamanlı olarak görüntüyü tarama işlemi gerçekleştirir ve burada bizde duruma göre işlemleri gerçekleştiririz. Bunun dışında cameraDidChangeTrackingState fonksiyonuda ARKit ile işlem yaparken bize kameranın durumu hakkında bilgi verir ve bizde buna göre işlem yenileme,silme ve başlatma olaylarını yönetiriz. Diğer kalan işlemler sizin SceneKit kullanabilirliğinize bırakılmış.

Not: Kodu denerken label ve button ekleyip outletlere bağlamayı unutmayın aksi halde uygulamanın kapanması ile sonuçlanacaktır.

Github link!

 

Kategori:iOS

Bu yazı yorumlara kapalı.

Copyright © 2022 Kenan Atmaca