简体   繁体   中英

Add shape to sphere surface in SceneKit

I'd like to be able to add shapes to the surface of a sphere using SceneKit . I started with a simple example where I'm just trying to color a portion of the sphere's surface another color. I'd like this to be an object that can be tapped, selected, etc... so my thought was to add shapes as SCNNodes using custom SCNShape objects for the geometry.

What I have now is a blue square that I'm drawing from a series of points and adding to the scene containing a red sphere. It basically ends up tangent to a point on the sphere, but the real goal is to draw it on the surface. Is there anything in SceneKit that will allow me to do this? Do I need to do some math/geometry to make it the same shape as the sphere or map to a sphere's coordinates? Is what I'm trying to do outside the scope of SceneKit?

If this question is way too broad I'd be glad if anyone could point me towards books or resources to learn what I'm missing. I'm totally new to SceneKit and 3D in general, just having fun playing around with some ideas.

Here's some playground code for what I have now:

import UIKit
import SceneKit
import XCPlayground

class SceneViewController: UIViewController {

    let sceneView = SCNView()

    private lazy var sphere: SCNSphere = {
        let sphere = SCNSphere(radius: 100.0)
        sphere.materials = [self.surfaceMaterial]
        return sphere
    }()

    private lazy var testScene: SCNScene = {
        let scene = SCNScene()
        let sphereNode: SCNNode = SCNNode(geometry: self.sphere)
        sphereNode.addChildNode(self.blueChildNode)

        scene.rootNode.addChildNode(sphereNode)
        //scene.rootNode.addChildNode(self.blueChildNode)
        return scene
    }()

    private lazy var surfaceMaterial: SCNMaterial = {
        let material = SCNMaterial()
        material.diffuse.contents = UIColor.redColor()
        material.specular.contents = UIColor(white: 0.6, alpha: 1.0)
        material.shininess = 0.3
        return material
    }()

    private lazy var blueChildNode: SCNNode = {
        let node: SCNNode = SCNNode(geometry: self.blueGeometry)
        node.position = SCNVector3(0, 0, 100)
        return node
    }()

    private lazy var blueGeometry: SCNShape = {
        let points: [CGPoint] = [
            CGPointMake(0, 0),
            CGPointMake(50, 0),
            CGPointMake(50, 50),
            CGPointMake(0, 50),
            CGPointMake(0, 0)]

        var pathRef: CGMutablePathRef = CGPathCreateMutable()
        CGPathAddLines(pathRef, nil, points, points.count)

        let bezierPath: UIBezierPath = UIBezierPath(CGPath: pathRef)
        let shape = SCNShape(path: bezierPath, extrusionDepth: 1)
        shape.materials = [self.blueNodeMaterial]
        return shape
    }()

    private lazy var blueNodeMaterial: SCNMaterial = {
        let material = SCNMaterial()
        material.diffuse.contents = UIColor.blueColor()
        return material
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        sceneView.frame = self.view.bounds
        sceneView.backgroundColor = UIColor.blackColor()
        self.view.addSubview(sceneView)

        sceneView.autoenablesDefaultLighting = true
        sceneView.allowsCameraControl = true

        sceneView.scene = testScene
    }
}

XCPShowView("SceneKit", view: SceneViewController().view)

If you want to map 2D content into the surface of a 3D SceneKit object, and have the 2D content be dynamic/interactive, one of the easiest solutions is to use SpriteKit for the 2D content. You can set your sphere's diffuse contents to an SKScene , and create/position/decorate SpriteKit nodes in that scene to arrange them on the face of the sphere.

If you want to have this content respond to tap events... Using hitTest in your SceneKit view gets you a SCNHitTestResult , and from that you can get texture coordinates for the hit point on the sphere. From texture coordinates you can convert to SKScene coordinates and spawn nodes, run actions, or whatever.

For further details, your best bet is probably Apple's SceneKitReel sample code project. This is the demo that introduced SceneKit for iOS at WWDC14. There's a "slide" in that demo where paint globs fly from the camera at a spinning torus and leave paint splashes where they hit it — the torus has a SpriteKit scene as its material, and the trick for leaving splashes on collisions is basically the same hit test -> texture coordinate -> SpriteKit coordinate approach outlined above.

David Rönnqvist's SceneKit book (available as an iBook) has an example (the EarthView example , a talking globe, chapter 5) that is worth looking at. That example constructs a 3D pushpin, which is then attached to the surface of a globe at the location of a tap.

Your problem is more complicated because you're constructing a shape that covers a segment of the sphere. Your "square" is really a spherical trapezium, a segment of the sphere bounded by four great circle arcs. I can see three possible approaches, depending on what you're ultimately looking for.

The simplest way to do it is to use an image as the material for the sphere's surface. That approach is well illustrated in the Ronnqvist EarthView example, which uses several images to show the earth's surface. Instead of drawing continents, you'd draw your square. This approach isn't suitable for interactivity, though. Look at SCNMaterial .

Another approach would be to use hit test results. That's documented on SCNSceneRenderer (which SCNView complies with) and SCNHitTest . Using the hit test results, you could pull out the face that was tapped, and then its geometry elements. This won't get you all the way home, though, because SceneKit uses triangles for SCNSphere , and you're looking for quads. You will also be limited to squares that line up with SceneKit's underlying wireframe representation.

If you want full control of where the "square" is drawn, including varying its angle relative to the equator, I think you'll have to build your own geometry from scratch. That means calculating the latitude/longitude of each corner point, then generating arcs between those points, then calculating a bunch of intermediate points along the arcs. You'll have to add a fudge factor, to raise the intermediate points slightly above the sphere's surface, and build up your own quads or triangle strips. Classes here are SCNGeometry , SCNGeometryElement , and SCNGeometrySource .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM