Steve
Steve

Reputation: 4975

Scaling SCNNode and its children

I am making a simple measuring app. Currently I place spheres as SCNNodes around the place and between nodes appears a label that displays the length of the line from node 1 to node 2.

This is how the labels are created:

func addLabel() {
    let plane = SCNPlane(width: 0.07, height: 0.02)
    plane.cornerRadius = plane.height / 10

    let sks = SKScene(size: CGSize(width: plane.width * 10e3, height: plane.height * 10e3))
    sks.backgroundColor = UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 0.7)

    currentLbl = SKLabelNode(text: "")
    currentLbl.fontSize = 110
    currentLbl.fontName = "Helvetica"
    currentLbl.verticalAlignmentMode = .center
    currentLbl.position = CGPoint(x: sks.frame.midX, y: sks.frame.midY)
    currentLbl.fontColor = .white

    sks.addChild(currentLbl)

    let material = SCNMaterial()
    material.isDoubleSided = true
    material.diffuse.contents = sks
    material.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)

    plane.materials = [material]
    let node = SCNNode(geometry: plane)
    node.constraints = [SCNBillboardConstraint()]
    node.position = SCNVector3Make(0, 0, 0)
    let (minBound, maxBound) = node.boundingBox
    node.pivot = SCNMatrix4MakeTranslation( (maxBound.x + minBound.x)/2, minBound.y, 0.02/2)

    lblNodes.append(node)
    currentLblNode = node
    sceneView.scene.rootNode.addChildNode(node)
}

I would like to apply a mathematical equation to the scale of these label nodes (in my update function) to maintain readability from a couple of metres.

var myNodes: [SCNNode] = []
let s = getMagicScalingNumber()

Say I obtained my scale factor as above and I have an array of SCNNodes, how can I scale all the nodes and their respective children so they stay visually proportional.

If SCNTransformConstraint() is an option for this, I would appreciate an example of how to implement it.


Edit: Just to clarify, I have tried

currentLblNode.scale = SCNVector3Make(s, s, s)

which does not seem to work.

Upvotes: 2

Views: 2039

Answers (1)

PongBongoSaurus
PongBongoSaurus

Reputation: 7385

I know this is very late, however I have put together an example which should point you in the right direction.

In your example you are essentially creating a holder SCNNode which contains your labels etc.

You can store these into an array of [SCNNode] and then transform the scale of these like so:

/// Updates The Contents Of Each Node Added To Our NodesAdded Array
///
/// - Parameters:
///   - nodes: [SCNNode]
///   - pointOfView: SCNNode
func updateScaleFromCameraForNodes(_ nodes: [SCNNode], fromPointOfView pointOfView: SCNNode){

    nodes.forEach { (node) in

        //1. Get The Current Position Of The Node
        let positionOfNode = SCNVector3ToGLKVector3(node.worldPosition)

        //2. Get The Current Position Of The Camera
        let positionOfCamera = SCNVector3ToGLKVector3(pointOfView.worldPosition)

        //3. Calculate The Distance From The Node To The Camera
        let distanceBetweenNodeAndCamera = GLKVector3Distance(positionOfNode, positionOfCamera)

        //4. Animate Their Scaling & Set Their Scale Based On Their Distance From The Camera
        SCNTransaction.begin()
        SCNTransaction.animationDuration = 0.5
        switch distanceBetweenNodeAndCamera {
        case 0 ... 0.5:
            node.simdScale = simd_float3(0.25, 0.25, 0.25)
        case 0.5 ... 1:
            node.simdScale = simd_float3(0.5, 0.5, 0.5)
        case 1 ... 1.5:
            node.simdScale = simd_float3(1, 1, 1)
        case 1.5 ... 2:
            node.simdScale = simd_float3(1.5, 1.5, 1.5)
        case 2 ... 2.5:
            node.simdScale = simd_float3(2, 2, 2)
        case 2.5 ... 3:
            node.simdScale = simd_float3(2.5, 2.5, 2.5)
        default:
            print("Default")
        }
        SCNTransaction.commit()
    }

}

Here I am just setting 'random' values to illustrate the concept of scale depending on the distance from the camera.

To put this better into context here is a little demo I have put together:

//--------------------------
// MARK: - ARSCNViewDelegate
//--------------------------

extension ViewController: ARSCNViewDelegate{

    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

        if !nodesAdded.isEmpty, let currentCameraPosition = self.sceneView.pointOfView {
            updateScaleFromCameraForNodes(nodesAdded, fromPointOfView: currentCameraPosition)
        }

    }
}

class ViewController: UIViewController {

     @IBOutlet var sceneView: ARSCNView!

     var nodesAdded = [SCNNode]()

    //-----------------------
    // MARK: - View LifeCycle
    //-----------------------

    override func viewDidLoad() {
        super.viewDidLoad()

        //1. Generate Our Three Box Nodes
        generateBoxNodes()

        //2. Run The Session
        let configuration = ARWorldTrackingConfiguration()
        sceneView.session.run(configuration)
        sceneView.delegate = self

    }

    /// Generates Three SCNNodes With An SCNBox Geometry & Places Them In A Holder Node
    func generateBoxNodes(){

        //1. Create An SCNNode To Hold All Of Our Content
        let holderNode = SCNNode()

        //2. Create An Array Of Colours For Each Face
        let colours: [UIColor] = [.red, .green, .blue, .purple, .cyan, .black]

        //3. Create An SCNNode Wih An SCNBox Geometry
        let boxNode = SCNNode()
        let boxGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.01)
        boxNode.geometry = boxGeometry

        //4. Create A Different Material For Each Face
        var materials = [SCNMaterial]()

        for i in 0..<5{
            let faceMaterial = SCNMaterial()
            faceMaterial.diffuse.contents = colours[i]
            materials.append(faceMaterial)
        }

        //5. Set The Geometries Materials
        boxNode.geometry?.materials = materials

        //6. Create Two More Nodes By Cloning The First One
        let secondBox = boxNode.flattenedClone()
        let thirdBox = boxNode.flattenedClone()

        //7. Position Them In A Line & Add To The Scene
        boxNode.position = SCNVector3(-0.2, 0, 0)
        secondBox.position = SCNVector3(0, 0, 0)
        thirdBox.position = SCNVector3(0.2, 0, 0)

        holderNode.addChildNode(boxNode)
        holderNode.addChildNode(secondBox)
        holderNode.addChildNode(thirdBox)
        holderNode.position = SCNVector3(0, 0, -1)
        self.sceneView.scene.rootNode.addChildNode(holderNode)

        nodesAdded.append(holderNode)
    }


    /// Updates The Contents Of Each Node Added To Our NodesAdded Array
    ///
    /// - Parameters:
    ///   - nodes: [SCNNode]
    ///   - pointOfView: SCNNode
    func updateScaleFromCameraForNodes(_ nodes: [SCNNode], fromPointOfView pointOfView: SCNNode){

        nodes.forEach { (node) in

            //1. Get The Current Position Of The Node
            let positionOfNode = SCNVector3ToGLKVector3(node.worldPosition)

            //2. Get The Current Position Of The Camera
            let positionOfCamera = SCNVector3ToGLKVector3(pointOfView.worldPosition)

            //3. Calculate The Distance From The Node To The Camera
            let distanceBetweenNodeAndCamera = GLKVector3Distance(positionOfNode, positionOfCamera)

            //4. Animate Their Scaling & Set Their Scale Based On Their Distance From The Camera
            SCNTransaction.begin()
            SCNTransaction.animationDuration = 0.5
            switch distanceBetweenNodeAndCamera {
            case 0 ... 0.5:
                node.simdScale = simd_float3(0.25, 0.25, 0.25)
            case 0.5 ... 1:
                node.simdScale = simd_float3(0.5, 0.5, 0.5)
            case 1 ... 1.5:
                node.simdScale = simd_float3(1, 1, 1)
            case 1.5 ... 2:
                node.simdScale = simd_float3(1.5, 1.5, 1.5)
            case 2 ... 2.5:
                node.simdScale = simd_float3(2, 2, 2)
            case 2.5 ... 3:
                node.simdScale = simd_float3(2.5, 2.5, 2.5)
            default:
                print("Default")
            }
            SCNTransaction.commit()
        }

    }


    override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) }

    override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) }

    override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() }

}

Hopefully it helps...

Upvotes: 5

Related Questions