44174


プログラミング初心者で、swiftで開発を始めた者です。  
SceneKitを使い、ジェスチャーで回転可能な、立方体のジオメトリを作成しました。

次に、この立方体の各面をタップした時に別のViewControllerに画面遷移させたいのですが、方法が分からず、困っています。

[code]

    import UIKit
    import SceneKit
    
    
    class KuruKuruViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        let scene = GameScene() //適用するsceneを設定する
    
    
        // SCNView 設定
        let scnView = self.view as! SCNView
        scnView.backgroundColor = UIColor.black
        scnView.scene = scene
        scnView.showsStatistics = true 
        scnView.allowsCameraControl = true 
    
        // CUBEジオメトリ
        let CUBE:SCNGeometry = SCNBox(width: 3, height: 3, length: 3, chamferRadius: 0.4)
        let geometryNode = SCNNode(geometry: CUBE)
        scene.rootNode.addChildNode(geometryNode) 
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        scnView.addGestureRecognizer(tapGesture)        
    
[/code]

追記１
SCNHitTestResultのfaceIndexプロパティを使い画面遷移を試みたが、faceIndexが返す値を「各面に0~5の値が割り当てられている」と間違える。

[code]

    @objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    
        let scnView = self.view as! SCNView
    
        let p = gestureRecognize.location(in: scnView) //pにタップした座標を渡す
        let hitResults = scnView.hitTest(p, options: [:]) //hitTestでタップした箇所のオブジェクトを検索
        var hitfaceIndex = (hitResults.first)?.faceIndex //初めにヒットしたオブジェクトの面のインデックスを返す
    
            if hitfaceIndex == 0 {
             self.performSegue(withIdentifier: "Segue1", sender: nil)
            }
            if hitfaceIndex == 1 {
                self.performSegue(withIdentifier: "Segue2", sender: nil)
            }
            if hitfaceIndex == 2 {
                self.performSegue(withIdentifier: "Segue3", sender: nil)
            }
            if hitfaceIndex == 3 {
                self.performSegue(withIdentifier: "Segue4", sender: nil)
            }
            if hitfaceIndex == 4 {
                self.performSegue(withIdentifier: "Segue5", sender: nil)
            }
            if hitfaceIndex == 5 {
                self.performSegue(withIdentifier: "Segue6", sender: nil)
            }
    
     }
    
[/code]

追記２
faceIndexが返す値を調べる為にprintを使ってシュミレータで画面タップした時に値を表示するようにした。結果、各面に0と1の値が表示された。(chamferRadiusが0の場合)  
各面で値が被るため、個別の画面遷移が出来ない。

[code]

    import UIKit
    import SceneKit
    
    
    class KuruKuruViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        let scene = GameScene()
    
        let scnView = self.view as! SCNView
        scnView.backgroundColor = UIColor.black
        scnView.scene = scene
        scnView.showsStatistics = true
        scnView.allowsCameraControl = true
    
        let CUBE:SCNGeometry = SCNBox(width: 3, height: 3, length: 3, chamferRadius: 0.0)
        let geometryNode = SCNNode(geometry: CUBE)
        scene.rootNode.addChildNode(geometryNode)
    
        //各面毎にマテリアルを設定するためにマテリアル情報を準備
        let m1 = SCNMaterial()
        let m2 = SCNMaterial()
        let m3 = SCNMaterial()
        let m4 = SCNMaterial()
        let m5 = SCNMaterial()
        let m6 = SCNMaterial()
    
        //マテリアルにイメージを設定
        m1.diffuse.contents = UIImage(named: "1")
        m2.diffuse.contents = UIImage(named: "2")
        m3.diffuse.contents = UIImage(named: "3")
        m4.diffuse.contents = UIImage(named: "4")
        m5.diffuse.contents = UIImage(named: "5")
        m6.diffuse.contents = UIImage(named: "6")
    
        //CUBEの各面のマテリアルに設定
        CUBE.materials = [m1, m2, m3, m4, m5, m6]
    
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        scnView.addGestureRecognizer(tapGesture) 
    
    }
    
    //CUBEの面をタップするとfaceIndexの値をprintする
    @objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    
        let scnView = self.view as! SCNView
        let p = gestureRecognize.location(in: scnView)
        let hitResults = scnView.hitTest(p, options: [:]) 
        if let number = hitResults.first?.faceIndex {
            print(number)
        }
    
     }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    }
    
[/code]

追記３( 解決 )
`SCNGeometry`に対して複数の`SCNMaterial`を設定した場合、同時に`SCNGeometryElement`も複数になり、`SCNHitTestResult`のプロパティ`faceIndex`では、各面0と1の値しか返さない事が判明した。  
そこで、同じ`SCNHitTestResult`のプロパティである`geometryIndex`を使ったところ、各面の値を取得することが出来た。  
そして、画面遷移のコードに取得した値を割り当てて、「立方体ジオメトリの各面タップ時に別々に画面遷移する」ことに成功した。

[code]

    import UIKit
    import SceneKit
    
    class KuruKuruViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        let scene = GameScene() //適用するsceneを設定する
    
        // SCNView 設定
        let scnView = self.view as! SCNView
        scnView.backgroundColor = UIColor.white
        scnView.scene = scene
        scnView.showsStatistics = true //統計情報を表示
        scnView.allowsCameraControl = true  //ジェスチャーで様々なカメラ操作を可能に
    
        // CUBEジオメトリ
        let CUBE:SCNGeometry = SCNBox(width: 3, height: 3, length: 3, chamferRadius: 0.0)
        let geometryNode = SCNNode(geometry: CUBE)
        scene.rootNode.addChildNode(geometryNode) //チルドノードをルートノードに適応
    
        //各面毎にマテリアルを設定するためにマテリアル情報を準備
        let m1 = SCNMaterial()
        let m2 = SCNMaterial()
        let m3 = SCNMaterial()
        let m4 = SCNMaterial()
        let m5 = SCNMaterial()
        let m6 = SCNMaterial()
    
        //マテリアルにイメージを設定
        m1.diffuse.contents = UIImage(named: "1")
        m2.diffuse.contents = UIImage(named: "2")
        m3.diffuse.contents = UIImage(named: "3")
        m4.diffuse.contents = UIImage(named: "4")
        m5.diffuse.contents = UIImage(named: "5")
        m6.diffuse.contents = UIImage(named: "6")
    
        //CUBEの各面のマテリアルに設定
        CUBE.materials = [m1, m2, m3, m4, m5, m6]
    
        //タップジェスチャーを追加
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        scnView.addGestureRecognizer(tapGesture) //viewをタップするとタップジェスチャーを追加 handleTap関数を実行
    
    }
    
    //<CUBE全体タップ>タップで画面遷移 handleTap関数を設定
    @objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    
        let scnView = self.view as! SCNView
        let p = gestureRecognize.location(in: scnView)
        let hitResults = scnView.hitTest(p, options: [:])
        let number = hitResults.first?.geometryIndex  // ジオメトリのエレメントの値を取得
    
            //画面遷移
            if number == 0 {
             self.performSegue(withIdentifier: "Segue1", sender: nil)
            }
            if number == 1 {
                self.performSegue(withIdentifier: "Segue2", sender: nil)
            }
            if number == 2 {
                self.performSegue(withIdentifier: "Segue3", sender: nil)
            }
            if number == 3 {
                self.performSegue(withIdentifier: "Segue4", sender: nil)
            }
            if number == 4 {
                self.performSegue(withIdentifier: "Segue5", sender: nil)
            }
            if number == 5 {
                self.performSegue(withIdentifier: "Segue6", sender: nil)
            }
    
     }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    }
    
[/code]


CC BY-SA 4.0
2018-05-16T18:36:45.690
44024
2018-05-29T06:04:34.410
2018-05-29T06:04:34.410
28540
28540
question
swift ios xcode swift4
SceneKitで作成した、立方体ジオメトリの各面タップ時に別々に画面遷移させたい！


_追記1に対して：_

引用プログラムコード中の、プロパティ`faceIndex`に関連するコメント行として、「//初めにヒットしたオブジェクトの面のインデックスを返す」と書いていらっしゃいますが、この「オブジェクトの面」とは、なにを指しているのでしょうか？その考察をどうやら質問者さんはなさっていないようで、この考察をしなければ、解決するものも解決しないままとなってしまいます。  
変更後のコードをすこし形を変えて、`faceIndex`と「オブジェクトの面」を調べるプログラムにしてみましょう。

[code]

    @objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    
        let scnView = self.view as! SCNView
    
        let p = gestureRecognize.location(in: scnView)
        let hitResults = scnView.hitTest(p, options: [:])
        if let number = hitResults.first?.faceIndex {
            print(number)
        }
    }
    
[/code]

Xcodeのプロジェクトから、シミュレータないし実機を起動し、Xcodeのコンソールに、`print()`関数から出力される`faceIndex`の値を、立方体の上をあちこちタップして調べてください。  
立方体`SCNBox`の`chamferRadius`を`0`すなわち角を丸めない状態だと、下図のような結果になります。下図は立方体を展開したものです。

[![画像の説明をここに入力](https://i.stack.imgur.com/eqBgx.png)](https://i.stack.imgur.com/eqBgx.png)

立方体を、三角形ポリゴンで分割した各面の番号が、`faceIndex`であることがわかります。

※`chamferRadius`を`0`より大きくして角を丸くすると、総ポリゴン数が増えるので、立方体の面を構成するポリゴンの番号は、変化します。

* * *

_ご質問の追記2に対して：_

ひとつのジオメトリ（`SCNGeometry`）に対して、複数のマテリアル（`SCNMaterial`）を設定すると、「各面に0と1の値が表示」されることになるようです。これは、同時にエレメント（`SCNGeometryElement`）も複数になるということが推論できます。SceneKitフレームワークのリファレンスを読んでも、そのようなことを書いている箇所を見つけられないので、わかりにくいのですが、もしこの推論が間違っていなければ、`SCNHitTestResult`のプロパティ`geometryIndex`を使えば、立方体の各面がそれぞれ別個のエレメントになっているはずなので、面の識別ができることになります。  
「追記1に対して：」のサンプルコードを次のように書き換えてみました。

[code]

    @objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    
        let scnView = self.view as! SCNView
    
        let p = gestureRecognize.location(in: scnView)
        let hitResults = scnView.hitTest(p, options: [:])
        if let number = hitResults.first?.geometryIndex { //変更
            print(number)
        }
    }
    
[/code]

これで各面の識別ができるか、ご確認ください。


CC BY-SA 4.0
2018-05-23T04:23:12.603
44174
2018-05-27T01:55:19.753
2018-05-27T01:55:19.753
18540
18540
44024
answer