SceneKit在用Swift触摸后得到纹理坐标

Phi*_*sen 3 objective-c ios scenekit swift ios8

我想在3D SceneKit场景中操纵2D纹理.因此我使用此代码获取本地坐标:

@IBAction func tap(sender: UITapGestureRecognizer) {
    var arr:NSArray = my3dView.hitTest(sender.locationInView(my3dView), options: NSDictionary(dictionary: [SCNHitTestFirstFoundOnlyKey:true]))
    var res:SCNHitTestResult = arr.firstObject as SCNHitTestResult

    var vect:SCNVector3 = res.localCoordinates}
Run Code Online (Sandbox Code Playgroud)

我从我的场景中读出纹理:

    var mat:SCNNode = myscene.rootNode.childNodes[0] as SCNNode
    var child:SCNNode = mat.childNodeWithName("ID12", recursively: false)
    var geo:SCNMaterial = child.geometry.firstMaterial
    var channel = geo.diffuse.mappingChannel        
    var textureimg:UIImage = geo.diffuse.contents as UIImage
Run Code Online (Sandbox Code Playgroud)

现在我想在接触点绘制纹理......我怎么能这样做?如何将我的坐标从触摸转换为纹理图像?

ric*_*ter 6

听起来你有两个问题.(甚至没有使用正则表达式.:))

首先,您需要获取轻敲点的纹理坐标 - 即对象表面上的2D纹理空间中的点.你已经差不多了.SCNHitTestResult提供那些textureCoordinatesWithMappingChannel方法.(您正在使用localCoordinates,它可以让您在命中测试结果中获得节点所拥有的3D空间中的一个点.)您似乎已经找到了有关映射通道的业务,因此您知道要传递给该方法的内容.

问题#2是如何绘制的.

你正在做正确的事情来获得材料的内容UIImage.一旦你有了它,你可以查看绘图UIGraphicsCGContext功能 - 创建一个图像UIGraphicsBeginImageContext,将现有图像绘制到其中,然后绘制你想要在点击点添加的任何新内容.之后,您可以获得正在绘制的图像UIGraphicsGetImageFromCurrentImageContext并将其设置为新diffuse.contents材料.然而,这可能不是最好的方式 - 你在CPU上抄袭一堆图像数据,而且代码也有点笨拙.

更好的方法可能是利用SceneKit和SpriteKit之间的集成.这样,您所有的2D绘图都发生在与3D绘图相同的GPU上下文中 - 并且代码更简单一些.

您可以将材质设置diffuse.contents为SpriteKit场景.(要使用UIImage您当前对该纹理所具有的效果,只需将其粘贴在SKSpriteNode填充场景的场景上.)一旦获得了纹理坐标,就可以在该点为场景添加一个精灵.

var nodeToDrawOn: SCNNode!
var skScene: SKScene!

func mySetup() { // or viewDidLoad, or wherever you do setup
    // whatever else you're doing for setup, plus:

    // 1. remember which node we want to draw on
    nodeToDrawOn = myScene.rootNode.childNodeWithName("ID12", recursively: true)

    // 2. set up that node's texture as a SpriteKit scene
    let currentImage = nodeToDrawOn.geometry!.firstMaterial!.diffuse.contents as UIImage
    skScene = SKScene(size: currentImage.size)
    nodeToDrawOn.geometry!.firstMaterial!.diffuse.contents = skScene

    // 3. put the currentImage into a background sprite for the skScene
    let background = SKSpriteNode(texture: SKTexture(image: currentImage))
    background.position = CGPoint(x: skScene.frame.midX, y: skScene.frame.midY)
    skScene.addChild(background)
}

@IBAction func tap(sender: UITapGestureRecognizer) {
    let results = my3dView.hitTest(sender.locationInView(my3dView), options: [SCNHitTestFirstFoundOnlyKey: true]) as [SCNHitTestResult]
    if let result = results.first {
        if result.node === nodeToDrawOn {
            // 1. get the texture coordinates
            let channel = nodeToDrawOn.geometry!.firstMaterial!.diffuse.mappingChannel
            let texcoord = result.textureCoordinatesWithMappingChannel(channel)

            // 2. place a sprite there
            let sprite = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width: 10, height: 10))
            // scale coords: texcoords go 0.0-1.0, skScene space is is pixels
            sprite.position.x = texcoord.x * skScene.size.width
            sprite.position.y = texcoord.y * skScene.size.height
            skScene.addChild(sprite)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有关SpriteKit方法的更多详细信息(在Objective-C中),请参阅WWDC14的Union Demo的SceneKit状态.这显示了一个SpriteKit场景,用作环面的纹理贴图,其中的球体被抛出 - 当球体与圆环碰撞时,它得到一个SCNHitTestResult并使用其texcoords在SpriteKit场景中创建一个油漆飞溅.


最后,对您的代码进行一些Swift风格的评论(与问题和答案无关):

  • 使用let而不是var在不需要重新分配值的地方,优化器将使您的代码更快.
  • res: SCNHitTestResult很少需要显式类型注释().
  • Swift词典被桥接NSDictionary,因此您可以将它们直接传递给需要的API NSDictionary.
  • 转换为Swift类型的数组(hitTest(...) as [SCNHitTestResult])可以使您不必转换内容.