-->

ARKit - How to contain SCNText within another SCNN

2019-05-21 19:03发布

问题:

I am trying to create a quote generator with simple text within a speech bubble in ARKit.

I can show the speech bubble with text, but the text always starts in the middle and overflows outside of the speech bubble.

Any help getting it align in the top left of the speech bubble and wrapping within the speech bubble would be appreciated.

Result

Classes

class SpeechBubbleNode: SCNNode {
    private let textNode = TextNode()

    var string: String? {
        didSet {
            textNode.string = string
        }
    }

    override init() {
        super.init()

        // Speech Bubble
        let plane = SCNPlane(width: 200.0, height: 100.0)
        plane.cornerRadius = 4.0
        plane.firstMaterial?.isDoubleSided = true
        geometry = plane

        // Text Node
        textNode.position = SCNVector3(position.x, position.y, position.z + 1.0)
//        textNode.position = convertPosition(SCNVector3(0.0, 0.0, 1.0), to: textNode)
//        textNode.position = SCNVector3(0.0, 0.0, position.z + 1.0)
        addChildNode(textNode)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

class TextNode: SCNNode {
    private let textGeometry = SCNText()

    var string: String? {
        didSet {
            updateTextContainerFrame()
            textGeometry.string = string
        }
    }

    override init() {
        super.init()

        textGeometry.truncationMode = CATextLayerTruncationMode.middle.rawValue
        textGeometry.isWrapped = true
        textGeometry.alignmentMode = CATextLayerAlignmentMode.left.rawValue

        let blackMaterial = SCNMaterial()
        blackMaterial.diffuse.contents = UIColor.black
        blackMaterial.locksAmbientWithDiffuse = true
        textGeometry.materials = [blackMaterial]

        geometry = textGeometry
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    private func updateTextContainerFrame() {
        let (min, max) = boundingBox
        let width = CGFloat(max.x - min.x)
        let height = CGFloat(max.y - min.y)
        print("width :",max.x - min.x,"height :",max.y - min.y,"depth :",max.z - min.z)
        textGeometry.containerFrame = CGRect(x: 0.0, y: 0.0, width: width, height: height)
//        textGeometry.containerFrame = CGRect(origin: .zero, size: CGSize(width: 1.0, height: 1.0))
    }
}

Implementation

private func makeSpeechBubbleNode(forBobbleheadNode bobbleheadNode: BobbleheadNode) {
    let node = SpeechBubbleNode()
    node.position = sceneView.scene.rootNode.convertPosition(bobbleheadNode.position, to: node)
    node.scale = SCNVector3(0.002, 0.002, 0.002)

    sceneView.scene.rootNode.addChildNode(speechBubbleNode)
    self.speechBubbleNode = speechBubbleNode

    speechBubbleNode.string = "Some random string that could be long and should wrap within speech bubble"
}

回答1:

If you only need a white box behind your text I achieved it by doing this in my renderer function:

func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {

        let node = SCNNode()

        ............

        let testPlane = SCNPlane(width: someWidth, height: someHeight)


        let testScene = SKScene(size: CGSize(width: 900, height: 900))
        testScene.backgroundColor = UIColor.white

        let str = SKLabelNode(text: "This is just a test")
        str.color = UIColor.black
        str.fontColor = UIColor.black
        str.fontSize = 45.5

        str.position = CGPoint(x: stuff.size.width / 2,
                                 y: stuff.size.height / 2)
        testScene.addChild(str)

        testPlane.firstMaterial?.diffuse.contents = testScene
        testPlane.firstMaterial?.isDoubleSided = true
        testPlane.firstMaterial?.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)

        let testNode = SCNNode(geometry: testPlane)


        testNode.eulerAngles.x = -.pi / 2
        testNode.position = SCNVector3Make(0.0,0.0,0.0)

        node.addChildNode(testNode)


}


回答2:

I had the same problem and finally I have solved it as following:

  • Create a SCNText and add it as a geometry to SCNNode:

    let string = "Coverin text with a plane :)"
    let text = SCNText(string: string, extrusionDepth: 0.1)
    text.font = UIFont.systemFont(ofSize: 1)
    text.flatness = 0.005
    let textNode = SCNNode(geometry: text)
    let fontScale: Float = 0.01
    textNode.scale = SCNVector3(fontScale, fontScale, fontScale)
    
  • Coordinate the text pivot form left bottom to center:

    let (min, max) = (text.boundingBox.min, text.boundingBox.max)
    let dx = min.x + 0.5 * (max.x - min.x)
    let dy = min.y + 0.5 * (max.y - min.y)
    let dz = min.z + 0.5 * (max.z - min.z)
    textNode.pivot = SCNMatrix4MakeTranslation(dx, dy, dz)
    
  • Create a PlaneNode and add the textNode as a childNode of the PlaneNode:

    let width = (max.x - min.x) * fontScale
    let height = (max.y - min.y) * fontScale
    let plane = SCNPlane(width: CGFloat(width), height: CGFloat(height))
    let planeNode = SCNNode(geometry: plane)
    planeNode.geometry?.firstMaterial?.diffuse.contents = UIColor.green.withAlphaComponent(0.5)
    planeNode.geometry?.firstMaterial?.isDoubleSided = true
    planeNode.position = textNode.position
    textNode.eulerAngles = planeNode.eulerAngles
    planeNode.addChildNode(textNode)
    
  • and at the end add the PlaneNode to sceneView:

    sceneView.scene.rootNode.addChildNode(planeNode)
    

and that's the result: