使用Swift的SceneKit在触摸后获取纹理坐标

4

我希望在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}

我可以使用以下代码从场景中读取纹理:

    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

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

1个回答

8
听起来你有两个问题。(甚至没有使用正则表达式。 :)
首先,你需要获取点击点的纹理坐标——也就是物体表面上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)
        }
    }
}

如需了解有关SpriteKit方法(使用Objective-C)的更多详细信息,请参见WWDC14的SceneKit State of the Union Demo。该演示展示了一个SpriteKit场景用作环面体的纹理映射,带有被抛向它的油漆球体 - 每当一个球体与环面体碰撞时,它会获得一个SCNHitTestResult并使用其纹理坐标在SpriteKit场景中创建油漆飞溅。


最后,关于您的代码的Swift风格注释(与问题和答案无关):

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

谢谢!我始终得到映射通道的值为-1。 - Philsen
我在测试中看到了0。你是在将SKScene分配为漫反射材质之前还是之后读取“mappingChannel”属性?你是否使用具有纹理坐标的几何体? - rickster
我该如何确定几何图形是否具有纹理坐标? - Philsen
请询问其纹理坐标源。或者,如果您确信您的几何体具有纹理坐标,请尝试将映射通道传递为0。 - rickster
我有几何文本坐标,但如果我将映射通道设为0,则会出现错误访问。 - Philsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接