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
。 完成后,您可以使用UIGraphics
和CGContext
函数查看绘图 – 使用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]
)可以使您不必转换内容。