Search code examples
swift3dsprite-kittexturesscenekit

How to color a 3D object with slide/tap (dynamically) in Swift 2.1


I have to build a Swift app that can color a 3D object with various gestures. I have a Collada file that I imported as a SceneKit object. I can't find a way to color it with tap. I tried to convert Obj-C from the WWDC example but it's not working for me.

I tried to apply a SpriteKit as texture on the 3D object but that's the result:

As the user taps the screen, the app should take the coordinates of the texture of the 3D object and then add in that point a SpriteKit node.

Before:

Before

After:

object after run

I don't know why the texture explode in that way.

Thank you!


Solution

  • The SpriteKit scene coordinate system doesn't map to texture coordinates in the same way an image does. Pixel coordinates in an image have the y-axis increasing downward; in a SpriteKit scene, the y-axis goes up.

    This is a lot more apparent if you just map that SpriteKit scene onto an SCNPlane that's the only thing in a SceneKit scene — you'll see that the image is flipped.

    Left: Image as loaded from bundle, Right: Image filling an SKScene mapped to a plane (with annotations)

    base image image in SKScene on SCNMaterial

    To fix this, you'll need a two-step coordinate transformation:

    1. Translate the origin y-coordinate up by the height of the SKScene
    2. Scale y-coordinates by -1 so that increasing coordinates go down, not up

    coordinate transformation

    You can do this either within the SKScene, or in the SCNMaterialProperty that texture-maps the SpriteKit content onto a SceneKit object. I favor the SCNMaterialProperty approach — just set the appropriate matrix for its contentsTransform property:

    let translate = SCNMatrix4MakeTranslation(0, 1, 0)
    let yFlippedTranslate = SCNMatrix4Scale(translate, 1, -1, 1)
    material.diffuse.contentsTransform = yFlippedTranslate
    

    Note that if you're also trying to map touch/click events into the SpriteKit scene using the material's texture coordinates (via SCNHitTestResult), you'll need to perform a similar transformation there:

    let texcoord = result.textureCoordinatesWithMappingChannel(0)
    sprite.position.x = texcoord.x * skScene.size.width
    sprite.position.y = (1 - texcoord.y) * skScene.size.height