Home > Software design >  Align a node with its neighbor in SceneKit
Align a node with its neighbor in SceneKit

Time:06-07

Using Swift 5.5, iOS 14 Trying to create a simple 3D bar chart and I immediately find myself in trouble.

I wrote this code

for i in stride(from: 0, to: 6, by: 0.5) {

    let rnd = CGFloat.random(in: 1.0...4.0)
    let targetGeometry = SCNBox(width: 0.5, 
                               height: rnd, 
                               length: 0.5, 
                        chamferRadius: 0.2)
    targetGeometry.firstMaterial?.fillMode = .lines
    targetGeometry.firstMaterial?.diffuse.contents = UIColor.blue
    let box = SCNNode(geometry: targetGeometry)
    
    box.simdPosition = SIMD3(x: 0, y: 0, z: 0)
    coreNode.addChildNode(box)
}

This works well, but all the bars a centred around their centre. But how can I ask SceneKit to change the alignment?

enter image description here

Almost got this working with this code...

box.simdPosition = SIMD3(x: Float(i) - 3, 
                         y: Float(rnd * 0.5), 
                         z: 0)

But the result isn't right... I want/need the bar to grow from the base.

https://youtu.be/KJgvdBFBfyc

How can I make this grow from the base?

--Updated--1

Tried Andy Jazz suggestion replacing simdposition with the following formulae.

box.simdPosition = SIMD3(x:box.simdPosition.x, y:0, z:0)
box.simdPivot.columns.3.y = box.boundingBox.max.y - Float(rnd * 0.5)

--Updated--2

But although the boxes align at the base, when I change the values they still grow at both ends before realigning. Here is a video.

https://youtu.be/kVbW31ycvWc

--Updated--3

Ok, Thanks Andy this works...

var rnds:[CGFloat] = []
for i in 0..<12 {
   let rnd = CGFloat.random(in: 1.0...2.0)
   let targetGeometry = SCNBox(width: 0.45, height: rnd, length: 0.45, chamferRadius: 0.25)
   let newNode = SCNNode(geometry: targetGeometry)
                
   sourceNodes[i].simdPivot.columns.3.y = newNode.boundingBox.min.y
   rnds.append(rnd)
 }
            
 for k in 0..<12 {
   let targetGeometry = SCNBox(width: 0.45, height: rnds[k], length: 0.45, chamferRadius: 0.25)
   targetGeometry.firstMaterial?.fillMode = .lines
   targetGeometry.firstMaterial?.diffuse.contents = UIColor.blue
   sourceNodes[k].geometry = targetGeometry
   sourceNodes[k].simdPosition = SIMD3(x: sourceNodes[k].simdPosition.x, y: 0, z: 0)
 }

--Update--4

Spoke too soon... animate this using a morpher and a SCNTransaction.. and it doesn't work.

var rnds:[CGFloat] = []
for i in 0..<12 {
  let rnd = CGFloat.random(in: 1.0...2.0)
  let targetGeometry = SCNBox(width: 0.45, height: rnd, length: 0.45, chamferRadius: 0.25)
  let newNode = SCNNode(geometry: targetGeometry)
                
  sourceNodes[i].simdPivot.columns.3.y = newNode.boundingBox.min.y
                rnds.append(rnd)
}
            
for k in 0..<12 {
  let targetGeometry = SCNBox(width: 0.45, height: rnds[k], length: 0.45, chamferRadius: 0.25)
  targetGeometry.firstMaterial?.fillMode = .lines
  targetGeometry.firstMaterial?.diffuse.contents = UIColor.blue
                
  let morpher = SCNMorpher()
  morpher.targets = [sourceNodes[k].geometry!,targetGeometry]
                sourceNodes[k].morpher = morpher
                
  SCNTransaction.begin()
  SCNTransaction.animationDuration = 1.8
  sourceNodes[k].morpher?.setWeight(1.0, forTargetAt: 1)
  sourceNodes[k].simdPosition = SIMD3(x: sourceNodes[k].simdPosition.x, y: 0, z: 0)
  SCNTransaction.commit()
}

--Updated--5

Tried this code to animate the change...

for k in 0..<12 {
  let targetGeometry = SCNBox(width: 0.45, height: rnds[k], length: 0.45, chamferRadius: 0.25)
  targetGeometry.firstMaterial?.fillMode = .lines
  targetGeometry.firstMaterial?.diffuse.contents = UIColor.blue
  sourceNodes[k].geometry = targetGeometry
                
  let morpher = SCNMorpher()
                
  morpher.targets = [sourceNodes[k].geometry!,targetGeometry]
  sourceNodes[k].morpher = morpher
                
  let animation = CABasicAnimation(keyPath: "morpher.weights")
  animation.fromValue = 0.0
  animation.toValue = 1.0
  animation.repeatCount = Float.infinity;
  animation.duration = 3.5
  animation.fillMode = .forwards
  animation.isRemovedOnCompletion = false
  sourceNodes[k].simdPosition = SIMD3(x: sourceNodes[k].simdPosition.x, y: 0, z: 0)

}
        

Sadly this only half works too, so the bars are level at the base, but the animation is simply a snap to the new value.

--Updated--6

SCNAction doesn't animate the changes either

for k in 0..<12 {
  let targetGeometry = SCNBox(width: 0.45, height: rnds[k], length: 0.45, chamferRadius: 0.25)
  targetGeometry.firstMaterial?.fillMode = .lines
  targetGeometry.firstMaterial?.diffuse.contents = UIColor.blue
  sourceNodes[k].geometry = targetGeometry
                
 let changeGeometry = SCNAction.customAction(duration: 10) { (node, elapsedTime) -> () in

                    
 sourceNodes[k].geometry = targetGeometry
 sourceNodes[k].simdPosition = SIMD3(x: sourceNodes[k].simdPosition.x, y: 0, z: 0)
}
                
                sourceNodes[k].runAction(changeGeometry)

CodePudding user response:

You have to position a pivot point of each bar to its base:

boxNode.simdPivot.columns.3.y = someFloatNumber

To reposition a pivot to bar's base use bounding box property:

boxNode.simdPivot.columns.3.y  = (boxNode.boundingBox.min.y as? simd_float1)!

After pivot's offset, reposition boxNode towards negative direction of Y-axis.

boxNode.position.y = 0
  • Related