bpedit
bpedit

Reputation: 1196

SceneKit: orient one node toward another in 3Dspace

I need to orient one node to point its Z-axis at another node in 3D. Yeah, the perfect job for the LookAtConstraint. And for most of my work LookAt is fine. But when I apply LookAt to a particular node, I can no longer animate that node's translation with SCNAction. Picture a hydrogen atom leaving a molecule as it ionizes. The orientation is needed to properly rotate the bond (a cylinder) bewteen the hydrogen and an oxygen atom on the molecule.

I can orient the bond FROM the oxygen TO the hydrogen and animate. But this disorients most of the other bonds which were getting by just fine with LookAt's.

I gave this a mighty try before realizing it answers a somewhat different question: Calculate rotations to look at a 3D point?

Upvotes: 2

Views: 631

Answers (2)

ooOlly
ooOlly

Reputation: 2127

My solution here. Deal with situation that node continuously translate in space and should always toward a position.

@discardableResult
func yew(_ node:SCNNode, toPosition position:SCNVector3) -> Float
{
    var eularAngle = SCNVector3Zero
    let tranform = node.transform
    var forward = GLKVector3Make(tranform.m31, tranform.m32, tranform.m33)
    var toWard = GLKVector3Make(position.x - node.position.x, position.y - node.position.y, position.z - node.position.z)
    forward = GLKVector3Normalize(GLKVector3Make(forward.x, 0, forward.z))
    toWard = GLKVector3Normalize(GLKVector3Make(toWard.x, 0, toWard.z))
    var dotProduct = GLKVector3DotProduct(forward,toWard)
    dotProduct = (dotProduct > 1) ? 1 : ((dotProduct < -1) ? -1 : dotProduct)
    var yew = acos(dotProduct)
    if yew < 0 {
        assert(false)
    }
    //toward is clockwise of forward
    let isCW = GLKVector3CrossProduct(forward, toWard).y < 0
    if isCW {
        yew = -yew
    }
    eularAngle.y = yew
    node.eulerAngles = SCNVector3Make(eularAngle.x + wrapperNode.eulerAngles.x,
                                      eularAngle.y + wrapperNode.eulerAngles.y,
                                      eularAngle.z + wrapperNode.eulerAngles.z)
    return yew
}
@discardableResult
func pitch(_ node:SCNNode, toPosition position:SCNVector3) -> Float{
    var eularAngle = SCNVector3Zero
    let tranform = node.transform
    var toWard = GLKVector3Make(position.x - node.position.x, position.y - node.position.y, position.z - node.position.z)
    var forward = GLKVector3Make(tranform.m31, tranform.m32, tranform.m33)
    forward = GLKVector3Normalize(forward)
    toWard = GLKVector3Normalize(toWard)
    var dotProduct = GLKVector3DotProduct(forward,toWard)
    dotProduct = (dotProduct > 1) ? 1 : ((dotProduct < -1) ? -1 : dotProduct)
    var pitch = acos(dotProduct)
    //toward is clockwise of forward, if right vector of model and crossProfuct.x has same direction
    let crossProduct = GLKVector3CrossProduct(forward, toWard)
    let isCW = (crossProduct.x <= 0) != (tranform.m11 <= 0)
    if isCW {
        pitch = -pitch
    }

    eularAngle.x = pitch

    node.eulerAngles = SCNVector3Make(eularAngle.x + node.eulerAngles.x,
                                      eularAngle.y + node.eulerAngles.y,
                                      eularAngle.z + node.eulerAngles.z)
    return pitch
}
func orient(_ node:SCNNode, toPosition position:SCNVector3) {
    self.yew(node, toPosition: position)
    self.pitch(node, toPosition: position)
}

Upvotes: 0

M. Bedi
M. Bedi

Reputation: 1086

I had a similar issue with a project. What I eventually realized was that I need to use multiple constraints. One for translation (movement) and the other using the look at constraint.

I would move the object and then apply the look at constraint; in this case, it was a camera following an objects being moved using actions. Code snippet follows:

let targetNodeConstraint = SCNLookAtConstraint(target: someObject) targetNodeConstraint.gimbalLockEnabled = true
let followObjectConstraint = SCNTransformConstraint(inWorldSpace: true, withBlock: { (node, matrix) -> SCNMatrix4 in let transformMatrix = SCNMatrix4MakeTranslation(
self.someObject.position.x - 1.0,
self.someObject.position.y, self.someObject.position.z + 1.0) return transformMatrix }) // Position the object behind the other object & rotate it to roadCamera.constraints = [followObjectConstraint, targetNodeConstraint]

The important thing to note is the order in which the constraints are added to the object using an array. In the code above, I am ignoring the current matrix before I apply a transform matrix (I should re-write this code someday)

The complete source code of this "experiment" is on GitHub as I try things out.

https://github.com/ManjitBedi/CubeTrip

Hopefully, this is helpful.

Upvotes: 1

Related Questions