identify face of a cube hit on touches began in Swift-Scene Kit

后端 未结 2 688
你的背包
你的背包 2020-12-06 08:21

I\'m tying to create an app with Scene kit to solve Rubix Cube. I\'ve my own dae file for the cube. Upon touches began I have the object that\'s been hit

  f         


        
相关标签:
2条回答
  • 2020-12-06 08:41

    We use this function to determine the face hit based on the localNormal value from the SCNHitResult.

    This function concludes a face has been hit if the magnitude of an axis is 1.

    It assumes exactly one and only one axis will have a magnitude equal to 1. The code will break otherwise. It also assumes a SCNBox geometry.

    In testing, this seems to work (for SCNBox geometries). The only complication is the localNormal value doesn't always return clean 0 values. Sometimes it returns values like -5.96046448e-08 so we use the round function to be safe in case the same applies to values near 1 but not exactly 1.

    We are new to SceneKit and 3D in general, so the code could be flawed. Please comment if you spot issues or potential optimizations.

    private func getHitFaceFromNormal(normal: SCNVector3) {
        if round(normal.x) == -1 {
            // Left face hit
        } else if round(normal.x) == 1 {
            // Right face hit
        } else if round(normal.y) == -1 {
            // Bottom face hit
        } else if round(normal.y) == 1 {
            // Top face hit
        } else if round(normal.z) == -1 {
            // Back face hit
        } else if round(normal.z) == 1 {
            // Front face hit
        } else {
            // Error, no face detected
        }
    }
    
    0 讨论(0)
  • 2020-12-06 08:42

    faceIndex looks promising, but will not actually get something you're likely to consider useful. The "faces" counted by that property are the tessellation of the mesh, so a cube won't be a collection of six quads, it'll be twelve triangles. (Or more: in some cases, even a flat-sided cube will be tessellated with more than one quad / two triangles per side. If you're using SCNBox you control these with widthSegmentCount etc.)

    Instead — especially if your cube is an SCNBox — the easiest solution might be to leverage this interesting behavior of that class:

    You can assign up to six SCNMaterial instances to a box—one for each side—with its materials property. The SCNBox class automatically creates SCNGeometryElement objects as needed to handle the number of materials.

    So, if you assign six materials, you'll get one for each side:

    let front = SCNMaterial()
    let right = SCNMaterial()
    let back = SCNMaterial()
    let left = SCNMaterial()
    let top = SCNMaterial()
    let bottom = SCNMaterial()
    cube.materials = [ front, right, back, left, top, bottom ]
    

    And in so doing, your SCNBox will have six geometry elements — one for each material, which corresponds to one for each side.

    Now, you can use hit testing to find out which geometry element was clicked:

    if let result = hitResults.first {
        let node = result.node
    
        // Find the material for the clicked element
        // (Indices match between the geometryElements and materials arrays)
        let material = node.geometry!.materials[result.geometryIndex]
    
        // Do something with that material, for example:
        let highlight = CABasicAnimation(keyPath: "diffuse.contents")
        highlight.toValue = NSColor.redColor()
        highlight.duration = 1.0
        highlight.autoreverses = true
        highlight.removedOnCompletion = true
        material.addAnimation(highlight, forKey: nil)
    }
    

    Or if you're not highlighting and want to use the face index for logic, here's the beginning of something you could use for that:

    enum CubeFace: Int {
        case Front, Right, Back, Left, Top, Bottom
    }
    
    // when processing hit test result:
    print("hit face: \(CubeFace(rawValue: result.geometryIndex))")
    
    0 讨论(0)
提交回复
热议问题