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

我想在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 

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

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

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

问题#2是如何绘制的。

你正在做正确的事情,把材料的内容作为UIImage 。 完成后,您可以使用UIGraphicsCGContext函数查看绘图 – 使用UIGraphics创建图像,将现有图像绘制到其中,然后在点击点绘制要添加的任何新内容。 之后,您可以使用UIGraphicsGetImageFromCurrentImageContext获取您正在绘制的图像,并将其设置为材质的新diffuse.contents 。 然而,这可能不是最好的方法 – 你在CPU上堆放一堆图像数据,而且代码也有点笨拙。

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

您可以将材质的diffuse.contents设置为SpriteKit场景。 (要使用当前用于该纹理的UIImage ,只需将其SKSpriteNode在填充场景的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的Union Demo的SceneKit状态。 这显示了一个SpriteKit场景,用作环面的纹理贴图,其中的球体被抛出 – 当球体与圆环碰撞时,它获得SCNHitTestResult并使用其texcoords在SpriteKit场景中创建油漆飞溅。


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

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